[MERGE] Merged lp:~openerp/openobject-addons/saas-3
bzr revid: psa@tinyerp.com-20140227114202-g2yw8pmsn4v0lm05
This commit is contained in:
commit
89de7d9f25
|
@ -37,8 +37,6 @@ from openerp.tools.translate import _
|
|||
from openerp.http import request
|
||||
from operator import itemgetter
|
||||
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -63,6 +61,7 @@ def calendar_id2real_id(calendar_id=None, with_date=False):
|
|||
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)
|
||||
|
@ -76,6 +75,7 @@ class calendar_attendee(osv.Model):
|
|||
Calendar Attendee Information
|
||||
"""
|
||||
_name = 'calendar.attendee'
|
||||
_rec_name = 'cn'
|
||||
_description = 'Attendee information'
|
||||
|
||||
def _compute_data(self, cr, uid, ids, name, arg, context=None):
|
||||
|
@ -334,22 +334,22 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
res = {}
|
||||
base_request = """
|
||||
SELECT
|
||||
crm.id,
|
||||
crm.date - interval '1' minute * calcul_delta.max_delta AS first_alarm,
|
||||
cal.id,
|
||||
cal.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
|
||||
WHEN cal.recurrency THEN cal.end_date - interval '1' minute * calcul_delta.min_delta
|
||||
ELSE cal.date_deadline - interval '1' minute * calcul_delta.min_delta
|
||||
END as last_alarm,
|
||||
crm.date as first_event_date,
|
||||
cal.date as first_event_date,
|
||||
CASE
|
||||
WHEN crm.recurrency THEN crm.end_date
|
||||
ELSE crm.date_deadline
|
||||
WHEN cal.recurrency THEN cal.end_date
|
||||
ELSE cal.date_deadline
|
||||
END as last_event_date,
|
||||
calcul_delta.min_delta,
|
||||
calcul_delta.max_delta,
|
||||
crm.rrule AS rule
|
||||
cal.rrule AS rule
|
||||
FROM
|
||||
calendar_event AS crm
|
||||
calendar_event AS cal
|
||||
RIGHT JOIN
|
||||
(
|
||||
SELECT
|
||||
|
@ -359,11 +359,11 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
LEFT JOIN calendar_alarm AS alarm ON alarm.id = rel.calendar_alarm_id
|
||||
WHERE alarm.type in %s
|
||||
GROUP BY rel.calendar_event_id
|
||||
) AS calcul_delta ON calcul_delta.calendar_event_id = crm.id
|
||||
) AS calcul_delta ON calcul_delta.calendar_event_id = cal.id
|
||||
"""
|
||||
|
||||
filter_user = """
|
||||
LEFT JOIN calendar_event_res_partner_rel AS part_rel ON part_rel.calendar_event_id = crm.id
|
||||
LEFT JOIN calendar_event_res_partner_rel AS part_rel ON part_rel.calendar_event_id = cal.id
|
||||
AND part_rel.res_partner_id = %s
|
||||
"""
|
||||
|
||||
|
@ -384,21 +384,14 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
#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)
|
||||
cr.execute("""SELECT *
|
||||
FROM ( %s ) 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 )
|
||||
""" % base_request, tuple_params)
|
||||
|
||||
for event_id, first_alarm, last_alarm, first_meeting, last_meeting, min_duration, max_duration, rule in cr.fetchall():
|
||||
res[event_id].update({
|
||||
res[event_id] = {
|
||||
'event_id': event_id,
|
||||
'first_alarm': first_alarm,
|
||||
'last_alarm': last_alarm,
|
||||
|
@ -407,7 +400,7 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
'min_duration': min_duration,
|
||||
'max_duration': max_duration,
|
||||
'rrule': rule
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
|
@ -433,83 +426,86 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
res.append(alert)
|
||||
return res
|
||||
|
||||
|
||||
def get_next_mail(self,cr,uid,context=None):
|
||||
cron = self.pool.get('ir.cron').search(cr,uid,[('model','ilike',self._name)],context=context)
|
||||
def get_next_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)
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
elif cron.interval_type == "minutes":
|
||||
cron_interval = cron.interval_number * 60
|
||||
elif cron.interval_type=="seconds":
|
||||
cron_interval = cron.interval_number
|
||||
elif cron.interval_type == "seconds":
|
||||
cron_interval = cron.interval_number
|
||||
|
||||
if not cron_interval:
|
||||
raise ("Cron delay for " + self._name + " can not be calculated :( !")
|
||||
|
||||
all_events = self.get_next_potential_limit_alarm(cr,uid,cron_interval,notif=False,context=context)
|
||||
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('calendar.event').browse(cr,uid,event,context=context)
|
||||
for event in all_events: # .values()
|
||||
max_delta = all_events[event]['max_duration']
|
||||
curEvent = self.pool.get('calendar.event').browse(cr, uid, event, context=context)
|
||||
if curEvent.recurrency:
|
||||
bFound = False
|
||||
LastFound = False
|
||||
for one_date in self.pool.get('calendar.event').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)
|
||||
for one_date in self.pool.get('calendar.event').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)
|
||||
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 precedent event had an alarm but not this one, we can stop the search for 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)
|
||||
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)
|
||||
self.do_mail_reminder(cr, uid, alert, context=context)
|
||||
|
||||
def get_next_notif(self,cr,uid,context=None):
|
||||
def get_next_notif(self, cr, uid, context=None):
|
||||
ajax_check_every_seconds = 300
|
||||
partner = self.pool.get('res.users').browse(cr,uid,uid,context=context).partner_id;
|
||||
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)
|
||||
|
||||
if not partner:
|
||||
return []
|
||||
|
||||
all_events = self.get_next_potential_limit_alarm(cr, uid, ajax_check_every_seconds, partner_id=partner.id, mail=False, context=context)
|
||||
|
||||
for event in all_events: # .values()
|
||||
max_delta = all_events[event]['max_duration'];
|
||||
curEvent = self.pool.get('calendar.event').browse(cr,uid,event,context=context)
|
||||
max_delta = all_events[event]['max_duration']
|
||||
curEvent = self.pool.get('calendar.event').browse(cr, uid, event, context=context)
|
||||
if curEvent.recurrency:
|
||||
bFound = False
|
||||
LastFound = False
|
||||
for one_date in self.pool.get("calendar.event").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)
|
||||
for one_date in self.pool.get("calendar.event").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.calendar_last_notif_ack, 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 precedent event had alarm but not this one, we can stop the search fot this event
|
||||
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 precedent 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)
|
||||
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.calendar_last_notif_ack, 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
|
||||
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:
|
||||
|
@ -520,7 +516,7 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
alarm = self.pool['calendar.alarm'].browse(cr, uid, alert['alarm_id'], context=context)
|
||||
|
||||
if alarm.type == 'email':
|
||||
res = self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, event.attendee_ids, template_xmlid='calendar_template_meeting_reminder', context=context)
|
||||
res = self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, [att.id for att in event.attendee_ids], template_xmlid='calendar_template_meeting_reminder', context=context)
|
||||
|
||||
return res
|
||||
|
||||
|
@ -777,7 +773,7 @@ class calendar_event(osv.Model):
|
|||
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]
|
||||
|
@ -790,7 +786,7 @@ class calendar_event(osv.Model):
|
|||
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]
|
||||
|
||||
|
@ -798,7 +794,7 @@ class calendar_event(osv.Model):
|
|||
'location': {
|
||||
'calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True,
|
||||
},
|
||||
'date': {
|
||||
'date': {
|
||||
'calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True,
|
||||
},
|
||||
}
|
||||
|
@ -979,7 +975,7 @@ class calendar_event(osv.Model):
|
|||
}
|
||||
return res
|
||||
|
||||
def get_search_fields(self,browse_event,order_fields,r_date=None):
|
||||
def get_search_fields(self, browse_event, order_fields, r_date=None):
|
||||
sort_fields = {}
|
||||
for ord in order_fields:
|
||||
if ord == 'id' and r_date:
|
||||
|
@ -989,17 +985,17 @@ class calendar_event(osv.Model):
|
|||
'If we sort on FK, we obtain a browse_record, so we need to sort on name_get'
|
||||
if type(browse_event[ord]) is openerp.osv.orm.browse_record:
|
||||
name_get = browse_event[ord].name_get()
|
||||
if len(name_get) and len(name_get[0])>=2:
|
||||
if len(name_get) and len(name_get[0]) >= 2:
|
||||
sort_fields[ord] = name_get[0][1]
|
||||
|
||||
return sort_fields
|
||||
|
||||
def get_recurrent_ids(self, cr, uid, event_id, domain, order=None, context=None):
|
||||
|
||||
"""Gives virtual event ids for recurring events
|
||||
"""Gives virtual event ids for recurring events
|
||||
This method gives ids of dates that comes between start date and end date of calendar views
|
||||
|
||||
@param order: The fields (comma separated, format "FIELD {DESC|ASC}") on which the events should be sorted
|
||||
|
||||
@param order: The fields (comma separated, format "FIELD {DESC|ASC}") on which the events should be sorted
|
||||
"""
|
||||
|
||||
if not context:
|
||||
|
@ -1024,7 +1020,7 @@ class calendar_event(osv.Model):
|
|||
for ev in self.browse(cr, uid, ids_to_browse, context=context):
|
||||
if not ev.recurrency or not ev.rrule:
|
||||
result.append(ev.id)
|
||||
result_data.append(self.get_search_fields(ev,order_fields))
|
||||
result_data.append(self.get_search_fields(ev, order_fields))
|
||||
continue
|
||||
|
||||
rdates = self.get_recurrent_date_by_event(cr, uid, ev, context=context)
|
||||
|
@ -1070,7 +1066,7 @@ class calendar_event(osv.Model):
|
|||
|
||||
if [True for item in new_pile if not item]:
|
||||
continue
|
||||
result_data.append(self.get_search_fields(ev,order_fields,r_date=r_date))
|
||||
result_data.append(self.get_search_fields(ev, order_fields, r_date=r_date))
|
||||
|
||||
if order_fields:
|
||||
def comparer(left, right):
|
||||
|
@ -1081,7 +1077,7 @@ class calendar_event(osv.Model):
|
|||
return 0
|
||||
|
||||
sort_params = [key.split()[0] if key[-4:].lower() != 'desc' else '-%s' % key.split()[0] for key in (order or self._order).split(',')]
|
||||
comparers = [ ((itemgetter(col[1:]), -1) if col[0] == '-' else (itemgetter(col), 1)) for col in sort_params]
|
||||
comparers = [((itemgetter(col[1:]), -1) if col[0] == '-' else (itemgetter(col), 1)) for col in sort_params]
|
||||
ids = [r['id'] for r in sorted(result_data, cmp=comparer)]
|
||||
|
||||
if isinstance(event_id, (str, int, long)):
|
||||
|
@ -1089,7 +1085,6 @@ class calendar_event(osv.Model):
|
|||
else:
|
||||
return ids
|
||||
|
||||
|
||||
def compute_rule_string(self, data):
|
||||
"""
|
||||
Compute rule string according to value type RECUR of iCalendar from the values given.
|
||||
|
@ -1123,7 +1118,7 @@ class calendar_event(osv.Model):
|
|||
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 '')
|
||||
((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 = ''
|
||||
|
@ -1219,12 +1214,12 @@ class calendar_event(osv.Model):
|
|||
res.update(self.check_partners_email(cr, uid, value[0][2], context=context))
|
||||
return res
|
||||
|
||||
def onchange_rec_day(self,cr,uid,id,date,mo,tu,we,th,fr,sa,su):
|
||||
def onchange_rec_day(self, cr, uid, id, date, mo, tu, we, th, fr, sa, su):
|
||||
""" set the start date according to the first occurence of rrule"""
|
||||
rrule_obj = self._get_empty_rrule_data()
|
||||
rrule_obj.update({
|
||||
'byday':True,
|
||||
'rrule_type':'weekly',
|
||||
'byday': True,
|
||||
'rrule_type': 'weekly',
|
||||
'mo': mo,
|
||||
'tu': tu,
|
||||
'we': we,
|
||||
|
@ -1232,12 +1227,11 @@ class calendar_event(osv.Model):
|
|||
'fr': fr,
|
||||
'sa': sa,
|
||||
'su': su,
|
||||
'interval':1
|
||||
'interval': 1
|
||||
})
|
||||
str_rrule = self.compute_rule_string(rrule_obj)
|
||||
first_occurence = list(rrule.rrulestr(str_rrule + ";COUNT=1", dtstart=datetime.strptime(date, "%Y-%m-%d %H:%M:%S"), forceset=True))[0]
|
||||
return {'value': { 'date' : first_occurence.strftime("%Y-%m-%d") + ' 00:00:00' } }
|
||||
|
||||
first_occurence = list(rrule.rrulestr(str_rrule + ";COUNT=1", dtstart=datetime.strptime(date, "%Y-%m-%d %H:%M:%S"), forceset=True))[0]
|
||||
return {'value': {'date': first_occurence.strftime("%Y-%m-%d") + ' 00:00:00'}}
|
||||
|
||||
def check_partners_email(self, cr, uid, partner_ids, context=None):
|
||||
""" Verify that selected partner_ids have an email_address defined.
|
||||
|
@ -1251,12 +1245,10 @@ class calendar_event(osv.Model):
|
|||
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,
|
||||
}
|
||||
}
|
||||
return {'warning': {
|
||||
'title': _('Email addresses not found'),
|
||||
'message': warning_msg,
|
||||
}}
|
||||
|
||||
# ----------------------------------------
|
||||
# OpenChatter
|
||||
|
@ -1365,8 +1357,7 @@ class calendar_event(osv.Model):
|
|||
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'))
|
||||
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
|
||||
|
@ -1381,29 +1372,25 @@ class calendar_event(osv.Model):
|
|||
|
||||
new_id = self._detach_one_event(cr, uid, ids[0], context=context)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'calendar.event',
|
||||
'view_mode': 'form',
|
||||
'res_id': new_id,
|
||||
'target': 'current',
|
||||
'flags': {'form': {'action_buttons': True, 'options' : { 'mode' : 'edit' } } }
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'calendar.event',
|
||||
'view_mode': 'form',
|
||||
'res_id': new_id,
|
||||
'target': 'current',
|
||||
'flags': {'form': {'action_buttons': True, 'options': {'mode': 'edit'}}}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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'''
|
||||
''' return True if changes are only to be made on the real ids'''
|
||||
for field in field_names:
|
||||
if field in ['date','active']:
|
||||
return True
|
||||
if field in ['date', 'active']:
|
||||
return True
|
||||
return False
|
||||
|
||||
context = context or {}
|
||||
|
||||
|
||||
if isinstance(ids, (str,int, long)):
|
||||
if isinstance(ids, (str, int, long)):
|
||||
if len(str(ids).split('-')) == 1:
|
||||
ids = [int(ids)]
|
||||
else:
|
||||
|
@ -1428,9 +1415,9 @@ class calendar_event(osv.Model):
|
|||
continue
|
||||
else:
|
||||
data = self.read(cr, uid, event_id, ['date', 'date_deadline', 'rrule', 'duration'])
|
||||
if data.get('rrule'):
|
||||
if data.get('rrule'):
|
||||
new_id = self._detach_one_event(cr, uid, event_id, values, context=None)
|
||||
|
||||
|
||||
res = super(calendar_event, self).write(cr, uid, ids, values, context=context)
|
||||
|
||||
# set end_date for calendar searching
|
||||
|
@ -1554,7 +1541,7 @@ class calendar_event(osv.Model):
|
|||
ids_to_unlink = []
|
||||
|
||||
# One time moved to google_Calendar, we can specify, if not in google, and not rec or get_inst = 0, we delete it
|
||||
for event_id in ids:
|
||||
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:
|
||||
ids_to_exclure.append(event_id)
|
||||
|
@ -1587,7 +1574,7 @@ class mail_message(osv.Model):
|
|||
|
||||
def _find_allowed_model_wise(self, cr, uid, doc_model, doc_dict, context=None):
|
||||
if doc_model == 'calendar.event':
|
||||
order = context.get('order', self._order)
|
||||
order = context.get('order', self._order)
|
||||
for virtual_id in self.pool[doc_model].get_recurrent_ids(cr, uid, doc_dict.keys(), [], order=order, 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)
|
||||
|
@ -1619,26 +1606,27 @@ class ir_http(osv.AbstractModel):
|
|||
|
||||
def _auth_method_calendar(self):
|
||||
token = request.params['token']
|
||||
db = request.params['db']
|
||||
db = request.params['db']
|
||||
|
||||
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)])
|
||||
attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token', '=', token)])
|
||||
if not attendee_id:
|
||||
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.partner_id.id != user.partner_id.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 attendee.partner_id.id != user.partner_id.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
|
||||
|
||||
|
||||
class invite_wizard(osv.osv_memory):
|
||||
_inherit = 'mail.wizard.invite'
|
||||
|
||||
|
|
|
@ -261,7 +261,7 @@
|
|||
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
|
||||
<strong style="margin-left:12px">Hello ${object.cn}</strong> ,<br/>
|
||||
<p style="margin-left:12px">The date of the meeting has been changed...<br/>
|
||||
The meeting created by ${object.event_id.user_id.partner_id.name} is now scheduled for : ${object.event_id.date}.</p>
|
||||
The meeting created by ${object.event_id.user_id.partner_id.name} is now scheduled for : ${object.event_id.display_time}.</p>
|
||||
</div>
|
||||
<div style="height: auto;margin-left:12px;margin-top:30px;">
|
||||
<table>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<record id="calendar_event_1" model="calendar.event">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.partner_root'),ref('base.res_partner_1'),ref('base.res_partner_6')])]"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.res_partner_6')])]"/>
|
||||
<field name="name">Follow-up for Project proposal</field>
|
||||
<field name="description">Meeting to discuss project plan and hash out the details of implementation.</field>
|
||||
<field eval="time.strftime('%Y-%m-03 10:20:00')" name="date"/>
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
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 meeting_invitation(http.Controller):
|
||||
|
||||
@http.route('/calendar/meeting/accept', type='http', auth="calendar")
|
||||
def accept(self, db, token, action, id,**kwargs):
|
||||
def accept(self, db, token, action, id, **kwargs):
|
||||
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')])
|
||||
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')
|
||||
|
@ -25,7 +23,7 @@ class meeting_invitation(http.Controller):
|
|||
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')])
|
||||
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')
|
||||
|
@ -36,13 +34,13 @@ class meeting_invitation(http.Controller):
|
|||
meeting_pool = registry.get('calendar.event')
|
||||
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)],[])
|
||||
|
||||
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))
|
||||
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in webmain.manifest_list('css', db=db))
|
||||
|
||||
return webmain.html_template % {
|
||||
'js': js,
|
||||
|
@ -50,7 +48,7 @@ class meeting_invitation(http.Controller):
|
|||
'modules': simplejson.dumps(webmain.module_boot(db)),
|
||||
'init': "s.calendar.event('%s', '%s', '%s', '%s' , '%s');" % (db, action, id, 'form', json.dumps(attendee_data)),
|
||||
}
|
||||
|
||||
|
||||
# Function used, in RPC to check every 5 minutes, if notification to do for an event or not
|
||||
@http.route('/calendar/notify', type='json', auth="none")
|
||||
def notify(self):
|
||||
|
@ -58,15 +56,14 @@ class meeting_invitation(http.Controller):
|
|||
uid = request.session.uid
|
||||
context = request.session.context
|
||||
with registry.cursor() as cr:
|
||||
res = registry.get("calendar.alarm_manager").get_next_notif(cr,uid,context=context)
|
||||
res = registry.get("calendar.alarm_manager").get_next_notif(cr, uid, context=context)
|
||||
return res
|
||||
|
||||
|
||||
@http.route('/calendar/notify_ack', type='json', auth="none")
|
||||
def notify_ack(self, type=''):
|
||||
registry = openerp.modules.registry.RegistryManager.get(request.session.db)
|
||||
uid = request.session.uid
|
||||
context = request.session.context
|
||||
with registry.cursor() as cr:
|
||||
res = registry.get("res.partner").calendar_last_notif(cr,uid,context=context)
|
||||
res = registry.get("res.partner").calendar_last_notif_ack(cr, uid, context=context)
|
||||
return res
|
||||
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
openerp.calendar = function(instance) {
|
||||
var _t = instance.web._t;
|
||||
var QWeb = instance.web.qweb;
|
||||
|
||||
instance.calendar = {};
|
||||
|
||||
|
||||
instance.web.WebClient = instance.web.WebClient.extend({
|
||||
|
||||
|
||||
get_notif_box: function(me) {
|
||||
return $(me).closest(".ui-notify-message-style");
|
||||
},
|
||||
get_next_notif: function() {
|
||||
var self= this;
|
||||
this.rpc("/calendar/notify")
|
||||
.then(
|
||||
.done(
|
||||
function(result) {
|
||||
_.each(result, function(res) {
|
||||
setTimeout(function() {
|
||||
|
@ -44,21 +47,33 @@ openerp.calendar = function(instance) {
|
|||
},res.timer * 1000);
|
||||
});
|
||||
}
|
||||
);
|
||||
)
|
||||
.fail(function (err, ev) {
|
||||
if (err.code === -32098) {
|
||||
// Prevent the CrashManager to display an error
|
||||
// in case of an xhr error not due to a server error
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
},
|
||||
check_notifications: function() {
|
||||
var self= this;
|
||||
self.get_next_notif();
|
||||
setInterval(function(){
|
||||
self.intervalNotif = setInterval(function(){
|
||||
self.get_next_notif();
|
||||
}, 5 * 60 * 1000 );
|
||||
}, 5 * 60 * 1000 );
|
||||
},
|
||||
|
||||
//Override the show_application of addons/web/static/src/js/chrome.js
|
||||
show_application: function() {
|
||||
this._super();
|
||||
this.check_notifications();
|
||||
},
|
||||
},
|
||||
//Override addons/web/static/src/js/chrome.js
|
||||
on_logout: function() {
|
||||
this._super();
|
||||
clearInterval(self.intervalNotif);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -23,15 +23,13 @@ from openerp.osv import fields, osv
|
|||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
#
|
||||
# calendar.event is defined in module calendar
|
||||
#
|
||||
|
||||
class calendar_event(osv.Model):
|
||||
""" Model for Calendar Event """
|
||||
_inherit = 'calendar.event'
|
||||
_columns = {
|
||||
'phonecall_id': fields.many2one ('crm.phonecall', 'Phonecall'),
|
||||
'opportunity_id': fields.many2one ('crm.lead', 'Opportunity', domain="[('type', '=', 'opportunity')]"),
|
||||
'phonecall_id': fields.many2one('crm.phonecall', 'Phonecall'),
|
||||
'opportunity_id': fields.many2one('crm.lead', 'Opportunity', domain="[('type', '=', 'opportunity')]"),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
|
@ -48,31 +46,11 @@ class calendar_attendee(osv.osv):
|
|||
_inherit = 'calendar.attendee'
|
||||
_description = 'Calendar Attendee'
|
||||
|
||||
def _compute_data(self, cr, uid, ids, name, arg, context=None):
|
||||
"""
|
||||
@param self: The object pointer
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of compute data’s IDs
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
name = name[0]
|
||||
result = super(calendar_attendee, self)._compute_data(cr, uid, ids, name, arg, context=context)
|
||||
|
||||
for attdata in self.browse(cr, uid, ids, context=context):
|
||||
id = attdata.id
|
||||
result[id] = {}
|
||||
if name == 'categ_id':
|
||||
if attdata.ref and 'categ_id' in attdata.ref._columns:
|
||||
result[id][name] = (attdata.ref.categ_id.id, attdata.ref.categ_id.name,)
|
||||
else:
|
||||
result[id][name] = False
|
||||
return result
|
||||
def _noop(self, cr, uid, ids, name, arg, context=None):
|
||||
return dict.fromkeys(ids, False)
|
||||
|
||||
_columns = {
|
||||
'categ_id': fields.function(_compute_data, \
|
||||
string='Event Type', type="many2one", \
|
||||
relation="crm.case.categ", multi='categ_id'),
|
||||
'categ_id': fields.function(_noop, string='Event Type', deprecated="Unused Field - TODO : Remove it in trunk", type="many2one", relation="crm.case.categ"),
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -127,7 +127,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
team_id = vals.get('section_id', False)
|
||||
data = self.browse(cr, uid, ids, context=context)[0]
|
||||
for lead_id in lead_ids:
|
||||
partner_id = self._create_partner(cr, uid, lead_id, data.action, data.partner_id, context=context)
|
||||
partner_id = self._create_partner(cr, uid, lead_id, data.action, data.partner_id.id, context=context)
|
||||
# FIXME: cannot pass user_ids as the salesman allocation only works in batch
|
||||
res = lead.convert_opportunity(cr, uid, [lead_id], partner_id, [], team_id, context=context)
|
||||
# FIXME: must perform salesman allocation in batch separately here
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
openerp.crm_partner_assign = function (instance) {
|
||||
instance.crm_partner_assign = instance.crm_partner_assign || {};
|
||||
instance.crm_partner_assign.next_or_list = function(parent) {
|
||||
if (parent.inner_widget.active_view === "form"){
|
||||
var form = parent.inner_widget.views.form.controller;
|
||||
form.dataset.remove_ids([form.dataset.ids[form.dataset.index]]);
|
||||
form.reload();
|
||||
if (!form.dataset.ids.length){
|
||||
parent.inner_widget.switch_mode('list');
|
||||
}
|
||||
}
|
||||
else{
|
||||
parent.inner_widget.views[parent.inner_widget.active_view].controller.reload();
|
||||
}
|
||||
parent.do_action({ type: 'ir.actions.act_window_close' });
|
||||
var view = parent.inner_widget.active_view;
|
||||
var controller = parent.inner_widget.views[view].controller;
|
||||
if (view === "form"){
|
||||
if (controller.dataset.size()) {
|
||||
controller.execute_pager_action('next');
|
||||
} else {
|
||||
controller.do_action('history_back');
|
||||
}
|
||||
}
|
||||
controller.do_action({ type: 'ir.actions.act_window_close' });
|
||||
if (view === "list"){
|
||||
controller.records.remove(controller.records.get(parent.dialog_widget.action.context.active_id));
|
||||
}
|
||||
};
|
||||
instance.web.client_actions.add("next_or_list", "instance.crm_partner_assign.next_or_list");
|
||||
}
|
|
@ -21,30 +21,22 @@
|
|||
|
||||
import operator
|
||||
import simplejson
|
||||
import re
|
||||
import urllib
|
||||
import warnings
|
||||
|
||||
from openerp import tools
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools.translate import _
|
||||
|
||||
from openerp.addons.web.http import request
|
||||
import werkzeug.utils
|
||||
|
||||
from datetime import datetime, timedelta, date
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil import parser
|
||||
import pytz
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.osv import osv
|
||||
from collections import namedtuple
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Meta(type):
|
||||
""" This Meta class allow to define class as a structure, and so instancied variable
|
||||
""" This Meta class allow to define class as a structure, and so instancied variable
|
||||
in __init__ to avoid to have side effect alike 'static' variable """
|
||||
def __new__(typ, name, parents, attrs):
|
||||
methods = dict((k, v) for k, v in attrs.iteritems()
|
||||
|
@ -63,9 +55,11 @@ class Meta(type):
|
|||
methods['__getitem__'] = getattr
|
||||
return type.__new__(typ, name, parents, methods)
|
||||
|
||||
|
||||
class Struct(object):
|
||||
__metaclass__ = Meta
|
||||
|
||||
|
||||
class OpenerpEvent(Struct):
|
||||
event = False
|
||||
found = False
|
||||
|
@ -77,6 +71,7 @@ class OpenerpEvent(Struct):
|
|||
attendee_id = False
|
||||
synchro = False
|
||||
|
||||
|
||||
class GmailEvent(Struct):
|
||||
event = False
|
||||
found = False
|
||||
|
@ -85,53 +80,51 @@ class GmailEvent(Struct):
|
|||
update = False
|
||||
status = False
|
||||
|
||||
|
||||
class SyncEvent(object):
|
||||
def __init__(self):
|
||||
self.OE = OpenerpEvent()
|
||||
self.GG = GmailEvent()
|
||||
self.GG = GmailEvent()
|
||||
self.OP = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self,key)
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def compute_OP(self):
|
||||
#If event are already in Gmail and in OpenERP
|
||||
#If event are already in Gmail and in OpenERP
|
||||
if self.OE.found and self.GG.found:
|
||||
#If the event has been deleted from one side, we delete on other side !
|
||||
if self.OE.status != self.GG.status:
|
||||
self.OP = Delete((self.OE.status and "OE") or (self.GG.status and "GG"),
|
||||
'The event has been deleted from one side, we delete on other side !' )
|
||||
#If event is not deleted !
|
||||
'The event has been deleted from one side, we delete on other side !')
|
||||
#If event is not deleted !
|
||||
elif self.OE.status and self.GG.status:
|
||||
if self.OE.update.split('.')[0] != self.GG.update.split('.')[0]:
|
||||
if self.OE.update < self.GG.update:
|
||||
tmpSrc = 'GG'
|
||||
elif self.OE.update > self.GG.update:
|
||||
tmpSrc = 'OE'
|
||||
assert tmpSrc in ['GG','OE']
|
||||
assert tmpSrc in ['GG', 'OE']
|
||||
|
||||
|
||||
#if self.OP.action == None:
|
||||
if self[tmpSrc].isRecurrence:
|
||||
if self[tmpSrc].status:
|
||||
self.OP = Update(tmpSrc, 'Only need to update, because i\'m active')
|
||||
self.OP = Update(tmpSrc, 'Only need to update, because i\'m active')
|
||||
else:
|
||||
self.OP = Exclude(tmpSrc, 'Need to Exclude (Me = First event from recurrence) from recurrence')
|
||||
|
||||
elif self[tmpSrc].isInstance:
|
||||
self.OP= Update(tmpSrc, 'Only need to update, because already an exclu');
|
||||
self.OP = Update(tmpSrc, 'Only need to update, because already an exclu')
|
||||
else:
|
||||
self.OP = Update(tmpSrc, 'Simply Update... I\'m a single event');
|
||||
#end-if self.OP.action == None:
|
||||
|
||||
self.OP = Update(tmpSrc, 'Simply Update... I\'m a single event')
|
||||
else:
|
||||
if not self.OE.synchro or self.OE.synchro.split('.')[0] < self.OE.update.split('.')[0]:
|
||||
self.OP = Update('OE','Event already updated by another user, but not synchro with my google calendar')
|
||||
self.OP = Update('OE', 'Event already updated by another user, but not synchro with my google calendar')
|
||||
#import ipdb; ipdb.set_trace();
|
||||
else:
|
||||
self.OP = NothingToDo("",'Not update needed')
|
||||
self.OP = NothingToDo("", 'Not update needed')
|
||||
else:
|
||||
self.OP = NothingToDo("", "Both are already deleted");
|
||||
self.OP = NothingToDo("", "Both are already deleted")
|
||||
|
||||
# New in openERP... Create on create_events of synchronize function
|
||||
elif self.OE.found and not self.GG.found:
|
||||
|
@ -139,64 +132,73 @@ class SyncEvent(object):
|
|||
if self.OE.status:
|
||||
self.OP = Delete('OE', 'Removed from GOOGLE')
|
||||
else:
|
||||
self.OP = NothingToDo("","Already Deleted in gmail and unlinked in OpenERP")
|
||||
self.OP = NothingToDo("", "Already Deleted in gmail and unlinked in OpenERP")
|
||||
elif self.GG.found and not self.OE.found:
|
||||
tmpSrc = 'GG'
|
||||
if not self.GG.status and not self.GG.isInstance:
|
||||
# don't need to make something... because event has been created and deleted before the synchronization
|
||||
self.OP = NothingToDo("", 'Nothing to do... Create and Delete directly')
|
||||
# don't need to make something... because event has been created and deleted before the synchronization
|
||||
self.OP = NothingToDo("", 'Nothing to do... Create and Delete directly')
|
||||
else:
|
||||
if self.GG.isInstance:
|
||||
if self[tmpSrc].status:
|
||||
self.OP = Exclude(tmpSrc, 'Need to create the new exclu')
|
||||
else:
|
||||
self.OP = Exclude(tmpSrc, 'Need to copy and Exclude')
|
||||
else:
|
||||
self.OP = Create(tmpSrc, 'New EVENT CREATE from GMAIL')
|
||||
if self.GG.isInstance:
|
||||
if self[tmpSrc].status:
|
||||
self.OP = Exclude(tmpSrc, 'Need to create the new exclu')
|
||||
else:
|
||||
self.OP = Exclude(tmpSrc, 'Need to copy and Exclude')
|
||||
else:
|
||||
self.OP = Create(tmpSrc, 'New EVENT CREATE from GMAIL')
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
myPrint = "---- A SYNC EVENT ---"
|
||||
myPrint += "\n ID OE: %s " % (self.OE.event and self.OE.event.id)
|
||||
myPrint += "\n ID GG: %s " % (self.GG.event and self.GG.event.get('id', False))
|
||||
myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name)
|
||||
myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', False))
|
||||
myPrint += "\n Found OE:%5s vs GG: %5s" % (self.OE.found, self.GG.found)
|
||||
myPrint += "\n Recurrence OE:%5s vs GG: %5s" % (self.OE.isRecurrence, self.GG.isRecurrence)
|
||||
myPrint += "\n Instance OE:%5s vs GG: %5s" % (self.OE.isInstance, self.GG.isInstance)
|
||||
myPrint += "\n Synchro OE: %10s " % (self.OE.synchro)
|
||||
myPrint += "\n Update OE: %10s " % (self.OE.update)
|
||||
myPrint += "\n Update GG: %10s " % (self.GG.update)
|
||||
myPrint += "\n Status OE:%5s vs GG: %5s" % (self.OE.status, self.GG.status)
|
||||
myPrint = "---- A SYNC EVENT ---"
|
||||
myPrint += "\n ID OE: %s " % (self.OE.event and self.OE.event.id)
|
||||
myPrint += "\n ID GG: %s " % (self.GG.event and self.GG.event.get('id', False))
|
||||
myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name)
|
||||
myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', False))
|
||||
myPrint += "\n Found OE:%5s vs GG: %5s" % (self.OE.found, self.GG.found)
|
||||
myPrint += "\n Recurrence OE:%5s vs GG: %5s" % (self.OE.isRecurrence, self.GG.isRecurrence)
|
||||
myPrint += "\n Instance OE:%5s vs GG: %5s" % (self.OE.isInstance, self.GG.isInstance)
|
||||
myPrint += "\n Synchro OE: %10s " % (self.OE.synchro)
|
||||
myPrint += "\n Update OE: %10s " % (self.OE.update)
|
||||
myPrint += "\n Update GG: %10s " % (self.GG.update)
|
||||
myPrint += "\n Status OE:%5s vs GG: %5s" % (self.OE.status, self.GG.status)
|
||||
if (self.OP is None):
|
||||
myPrint += "\n Action %s" % "---!!!---NONE---!!!---"
|
||||
myPrint += "\n Action %s" % "---!!!---NONE---!!!---"
|
||||
else:
|
||||
myPrint += "\n Action %s" % type(self.OP).__name__
|
||||
myPrint += "\n Source %s" % (self.OP.src)
|
||||
myPrint += "\n comment %s" % (self.OP.info)
|
||||
myPrint += "\n Action %s" % type(self.OP).__name__
|
||||
myPrint += "\n Source %s" % (self.OP.src)
|
||||
myPrint += "\n comment %s" % (self.OP.info)
|
||||
return myPrint
|
||||
|
||||
|
||||
class SyncOperation(object):
|
||||
def __init__(self, src,info, **kw):
|
||||
def __init__(self, src, info, **kw):
|
||||
self.src = src
|
||||
self.info = info
|
||||
for k,v in kw.items():
|
||||
setattr(self,k,v)
|
||||
for k, v in kw.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __str__(self):
|
||||
return 'in__STR__'
|
||||
|
||||
|
||||
class Create(SyncOperation):
|
||||
pass
|
||||
|
||||
|
||||
class Update(SyncOperation):
|
||||
pass
|
||||
|
||||
|
||||
class Delete(SyncOperation):
|
||||
pass
|
||||
|
||||
|
||||
class NothingToDo(SyncOperation):
|
||||
pass
|
||||
|
||||
|
||||
class Exclude(SyncOperation):
|
||||
pass
|
||||
|
||||
|
@ -205,9 +207,9 @@ class google_calendar(osv.AbstractModel):
|
|||
STR_SERVICE = 'calendar'
|
||||
_name = 'google.%s' % STR_SERVICE
|
||||
|
||||
def generate_data(self, cr, uid, event, context=None):
|
||||
def generate_data(self, cr, uid, event, context=None):
|
||||
if event.allday:
|
||||
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) , context=context).isoformat('T').split('T')[0]
|
||||
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0]
|
||||
end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=event.duration), context=context).isoformat('T').split('T')[0]
|
||||
type = 'date'
|
||||
vstype = 'dateTime'
|
||||
|
@ -220,72 +222,71 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
for attendee in event.attendee_ids:
|
||||
attendee_list.append({
|
||||
'email':attendee.email or 'NoEmail@mail.com',
|
||||
'displayName':attendee.partner_id.name,
|
||||
'responseStatus':attendee.state or 'needsAction',
|
||||
'email': attendee.email or 'NoEmail@mail.com',
|
||||
'displayName': attendee.partner_id.name,
|
||||
'responseStatus': attendee.state or 'needsAction',
|
||||
})
|
||||
data = {
|
||||
"summary": event.name or '',
|
||||
"description": event.description or '',
|
||||
"start":{
|
||||
type:start_date,
|
||||
vstype:None,
|
||||
'timeZone':'UTC'
|
||||
},
|
||||
"end":{
|
||||
type:end_date,
|
||||
vstype:None,
|
||||
'timeZone':'UTC'
|
||||
},
|
||||
"attendees":attendee_list,
|
||||
"location":event.location or '',
|
||||
"visibility":event['class'] or 'public',
|
||||
"start": {
|
||||
type: start_date,
|
||||
vstype: None,
|
||||
'timeZone': 'UTC'
|
||||
},
|
||||
"end": {
|
||||
type: end_date,
|
||||
vstype: None,
|
||||
'timeZone': 'UTC'
|
||||
},
|
||||
"attendees": attendee_list,
|
||||
"location": event.location or '',
|
||||
"visibility": event['class'] or 'public',
|
||||
}
|
||||
if event.recurrency and event.rrule:
|
||||
data["recurrence"]=["RRULE:"+event.rrule]
|
||||
data["recurrence"] = ["RRULE:" + event.rrule]
|
||||
|
||||
if not event.active:
|
||||
data["state"] = "cancelled"
|
||||
|
||||
if not self.get_need_synchro_attendee(cr,uid,context=context):
|
||||
if not self.get_need_synchro_attendee(cr, uid, context=context):
|
||||
data.pop("attendees")
|
||||
|
||||
return data
|
||||
|
||||
def create_an_event(self, cr, uid,event, context=None):
|
||||
def create_an_event(self, cr, uid, event, context=None):
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
data = self.generate_data(cr, uid,event, context=context)
|
||||
data = self.generate_data(cr, uid, event, context=context)
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary',urllib.quote('id,updated'),self.get_token(cr,uid,context))
|
||||
url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary', urllib.quote('id,updated'), self.get_token(cr, uid, context))
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
data_json = simplejson.dumps(data)
|
||||
|
||||
return gs_pool._do_request(cr, uid, url, data_json, headers, type='POST', context=context)
|
||||
|
||||
def delete_an_event(self, cr, uid,event_id, context=None):
|
||||
def delete_an_event(self, cr, uid, event_id, context=None):
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
params = {
|
||||
'access_token' : self.get_token(cr,uid,context)
|
||||
}
|
||||
'access_token': self.get_token(cr, uid, context)
|
||||
}
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
url = "/calendar/v3/calendars/%s/events/%s" % ('primary',event_id)
|
||||
url = "/calendar/v3/calendars/%s/events/%s" % ('primary', event_id)
|
||||
|
||||
return gs_pool._do_request(cr, uid, url, params, headers, type='DELETE', context=context)
|
||||
|
||||
def get_event_dict(self,cr,uid,token=False,nextPageToken=False,context=None):
|
||||
def get_event_dict(self, cr, uid, token=False, nextPageToken=False, context=None):
|
||||
if not token:
|
||||
token = self.get_token(cr,uid,context)
|
||||
token = self.get_token(cr, uid, context)
|
||||
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
params = {
|
||||
'fields': 'items,nextPageToken',
|
||||
'access_token' : token,
|
||||
'maxResults':1000,
|
||||
'timeMin': self.get_start_time_to_synchro(cr,uid,context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
|
||||
|
||||
'fields': 'items,nextPageToken',
|
||||
'access_token': token,
|
||||
'maxResults': 1000,
|
||||
'timeMin': self.get_start_time_to_synchro(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
|
||||
}
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
|
||||
|
@ -301,50 +302,50 @@ class google_calendar(osv.AbstractModel):
|
|||
google_events_dict[google_event['id']] = google_event
|
||||
|
||||
if content.get('nextPageToken', False):
|
||||
google_events_dict.update(self.get_event_dict(cr,uid,token,content['nextPageToken'],context=context))
|
||||
return google_events_dict
|
||||
google_events_dict.update(self.get_event_dict(cr, uid, token, content['nextPageToken'], context=context))
|
||||
return google_events_dict
|
||||
|
||||
def update_to_google(self, cr, uid, oe_event, google_event, context):
|
||||
calendar_event = self.pool['calendar.event']
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events/%s?fields=%s&access_token=%s" % ('primary', google_event['id'],'id,updated', self.get_token(cr,uid,context))
|
||||
url = "/calendar/v3/calendars/%s/events/%s?fields=%s&access_token=%s" % ('primary', google_event['id'], 'id,updated', self.get_token(cr, uid, context))
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
data = self.generate_data(cr,uid ,oe_event, context)
|
||||
data = self.generate_data(cr, uid, oe_event, context)
|
||||
data['sequence'] = google_event.get('sequence', 0)
|
||||
data_json = simplejson.dumps(data)
|
||||
|
||||
content = gs_pool._do_request(cr, uid, url, data_json, headers, type='PATCH', context=context)
|
||||
|
||||
update_date = datetime.strptime(content['updated'],"%Y-%m-%dT%H:%M:%S.%fz")
|
||||
calendar_event.write(cr, uid, [oe_event.id], {'oe_update_date':update_date})
|
||||
update_date = datetime.strptime(content['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
|
||||
calendar_event.write(cr, uid, [oe_event.id], {'oe_update_date': update_date})
|
||||
|
||||
if context['curr_attendee']:
|
||||
self.pool['calendar.attendee'].write(cr,uid,[context['curr_attendee']], {'oe_synchro_date':update_date},context)
|
||||
self.pool['calendar.attendee'].write(cr, uid, [context['curr_attendee']], {'oe_synchro_date': update_date}, context)
|
||||
|
||||
def update_an_event(self, cr, uid,event, context=None):
|
||||
def update_an_event(self, cr, uid, event, context=None):
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
data = self.generate_data(cr, uid,event, context=context)
|
||||
data = self.generate_data(cr, uid, event, context=context)
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events/%s" % ('primary', event.google_internal_event_id)
|
||||
headers = {}
|
||||
data['access_token'] = self.get_token(cr,uid,context)
|
||||
data['access_token'] = self.get_token(cr, uid, context)
|
||||
|
||||
response = gs_pool._do_request(cr, uid, url, data, headers, type='GET', context=context)
|
||||
#TO_CHECK : , if http fail, no event, do DELETE ?
|
||||
return response
|
||||
|
||||
def update_recurrent_event_exclu(self, cr, uid,instance_id,event_ori_google_id,event_new, context=None):
|
||||
def update_recurrent_event_exclu(self, cr, uid, instance_id, event_ori_google_id, event_new, context=None):
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
data = self.generate_data(cr, uid,event_new, context=context)
|
||||
data = self.generate_data(cr, uid, event_new, context=context)
|
||||
|
||||
data['recurringEventId'] = event_ori_google_id
|
||||
data['originalStartTime'] = event_new.recurrent_id_date
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events/%s?access_token=%s" % ('primary', instance_id,self.get_token(cr,uid,context))
|
||||
headers = { 'Content-type': 'application/json'}
|
||||
url = "/calendar/v3/calendars/%s/events/%s?access_token=%s" % ('primary', instance_id, self.get_token(cr, uid, context))
|
||||
headers = {'Content-type': 'application/json'}
|
||||
|
||||
data['sequence'] = self.get_sequence(cr, uid, instance_id, context)
|
||||
|
||||
|
@ -353,41 +354,41 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
def update_from_google(self, cr, uid, event, single_event_dict, type, context):
|
||||
if context is None:
|
||||
context= []
|
||||
context = []
|
||||
|
||||
calendar_event = self.pool['calendar.event']
|
||||
res_partner_obj = self.pool['res.partner']
|
||||
calendar_attendee_obj = self.pool['calendar.attendee']
|
||||
user_obj = self.pool['res.users']
|
||||
myPartnerID = user_obj.browse(cr,uid,uid,context).partner_id.id
|
||||
myPartnerID = user_obj.browse(cr, uid, uid, context).partner_id.id
|
||||
attendee_record = []
|
||||
partner_record = [(4,myPartnerID)]
|
||||
partner_record = [(4, myPartnerID)]
|
||||
result = {}
|
||||
|
||||
if single_event_dict.get('attendees',False):
|
||||
if single_event_dict.get('attendees', False):
|
||||
for google_attendee in single_event_dict['attendees']:
|
||||
if type == "write":
|
||||
for oe_attendee in event['attendee_ids']:
|
||||
if oe_attendee.email == google_attendee['email']:
|
||||
calendar_attendee_obj.write(cr, uid,[oe_attendee.id] ,{'state' : google_attendee['responseStatus']},context=context)
|
||||
calendar_attendee_obj.write(cr, uid, [oe_attendee.id], {'state': google_attendee['responseStatus']}, context=context)
|
||||
google_attendee['found'] = True
|
||||
continue
|
||||
|
||||
if google_attendee.get('found',False):
|
||||
if google_attendee.get('found', False):
|
||||
continue
|
||||
if self.get_need_synchro_attendee(cr,uid,context=context):
|
||||
attendee_id = res_partner_obj.search(cr, uid,[('email', '=', google_attendee['email'])], context=context)
|
||||
if not attendee_id:
|
||||
attendee_id = [res_partner_obj.create(cr, uid,{'email': google_attendee['email'],'customer': False, 'name': google_attendee.get("displayName",False) or google_attendee['email'] }, context=context)]
|
||||
if self.get_need_synchro_attendee(cr, uid, context=context):
|
||||
attendee_id = res_partner_obj.search(cr, uid, [('email', '=', google_attendee['email'])], context=context)
|
||||
if not attendee_id:
|
||||
attendee_id = [res_partner_obj.create(cr, uid, {'email': google_attendee['email'], 'customer': False, 'name': google_attendee.get("displayName", False) or google_attendee['email']}, context=context)]
|
||||
attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
|
||||
partner_record.append((4, attendee.get('id')))
|
||||
attendee['partner_id'] = attendee.pop('id')
|
||||
attendee['state'] = google_attendee['responseStatus']
|
||||
attendee_record.append((0, 0, attendee))
|
||||
|
||||
|
||||
UTC = pytz.timezone('UTC')
|
||||
if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled
|
||||
if single_event_dict['start'].get('dateTime',False) and single_event_dict['end'].get('dateTime',False):
|
||||
if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled
|
||||
if single_event_dict['start'].get('dateTime', False) and single_event_dict['end'].get('dateTime', False):
|
||||
date = parser.parse(single_event_dict['start']['dateTime'])
|
||||
date_deadline = parser.parse(single_event_dict['end']['dateTime'])
|
||||
delta = date_deadline.astimezone(UTC) - date.astimezone(UTC)
|
||||
|
@ -402,8 +403,8 @@ class google_calendar(osv.AbstractModel):
|
|||
delta = (d_end - d_start)
|
||||
allday = True
|
||||
|
||||
result['duration'] = (delta.seconds / 60) / 60.0 + delta.days *24
|
||||
update_date = datetime.strptime(single_event_dict['updated'],"%Y-%m-%dT%H:%M:%S.%fz")
|
||||
result['duration'] = (delta.seconds / 60) / 60.0 + delta.days * 24
|
||||
update_date = datetime.strptime(single_event_dict['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
|
||||
result.update({
|
||||
'date': date,
|
||||
'date_deadline': date_deadline,
|
||||
|
@ -413,17 +414,17 @@ class google_calendar(osv.AbstractModel):
|
|||
'attendee_ids': attendee_record,
|
||||
'partner_ids': list(set(partner_record)),
|
||||
|
||||
'name': single_event_dict.get('summary','Event'),
|
||||
'description': single_event_dict.get('description',False),
|
||||
'location':single_event_dict.get('location',False),
|
||||
'class':single_event_dict.get('visibility','public'),
|
||||
'oe_update_date':update_date,
|
||||
'name': single_event_dict.get('summary', 'Event'),
|
||||
'description': single_event_dict.get('description', False),
|
||||
'location': single_event_dict.get('location', False),
|
||||
'class': single_event_dict.get('visibility', 'public'),
|
||||
'oe_update_date': update_date,
|
||||
# 'google_internal_event_id': single_event_dict.get('id',False),
|
||||
})
|
||||
|
||||
if single_event_dict.get("recurrence",False):
|
||||
if single_event_dict.get("recurrence", False):
|
||||
rrule = [rule for rule in single_event_dict["recurrence"] if rule.startswith("RRULE:")][0][6:]
|
||||
result['rrule']=rrule
|
||||
result['rrule'] = rrule
|
||||
|
||||
if type == "write":
|
||||
res = calendar_event.write(cr, uid, event['id'], result, context=context)
|
||||
|
@ -435,77 +436,71 @@ class google_calendar(osv.AbstractModel):
|
|||
res = calendar_event.create(cr, uid, result, context=context)
|
||||
|
||||
if context['curr_attendee']:
|
||||
self.pool['calendar.attendee'].write(cr,uid,[context['curr_attendee']], {'oe_synchro_date':update_date,'google_internal_event_id': single_event_dict.get('id',False)},context)
|
||||
self.pool['calendar.attendee'].write(cr, uid, [context['curr_attendee']], {'oe_synchro_date': update_date, 'google_internal_event_id': single_event_dict.get('id', False)}, context)
|
||||
return res
|
||||
|
||||
def synchronize_events(self, cr, uid, ids, context=None):
|
||||
gc_obj = self.pool['google.calendar']
|
||||
|
||||
# Create all new events from OpenERP into Gmail, if that is not recurrent event
|
||||
self.create_new_events(cr, uid, context=context)
|
||||
|
||||
self.bind_recurring_events_to_google(cr, uid, context)
|
||||
|
||||
|
||||
res = self.update_events(cr, uid, context)
|
||||
|
||||
return {
|
||||
"status" : res and "need_refresh" or "no_new_event_form_google",
|
||||
"url" : ''
|
||||
}
|
||||
"status": res and "need_refresh" or "no_new_event_form_google",
|
||||
"url": ''
|
||||
}
|
||||
|
||||
def create_new_events(self, cr, uid, context=None):
|
||||
gc_pool = self.pool['google.calendar']
|
||||
ev_obj = self.pool['calendar.event']
|
||||
att_obj = self.pool['calendar.attendee']
|
||||
user_obj = self.pool['res.users']
|
||||
myPartnerID = user_obj.browse(cr,uid,uid,context=context).partner_id.id
|
||||
myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
|
||||
|
||||
context_norecurrent = context.copy()
|
||||
context_norecurrent['virtual_id'] = False
|
||||
|
||||
my_att_ids = att_obj.search(cr, uid,[ ('partner_id', '=', myPartnerID),
|
||||
('google_internal_event_id', '=', False),
|
||||
'|',
|
||||
('event_id.date_deadline','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
('event_id.end_date','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
], context=context_norecurrent)
|
||||
my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID),
|
||||
('google_internal_event_id', '=', False),
|
||||
'|',
|
||||
('event_id.date_deadline', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
('event_id.end_date', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
], context=context_norecurrent)
|
||||
|
||||
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
|
||||
for att in att_obj.browse(cr, uid, my_att_ids, context=context):
|
||||
if not att.event_id.recurrent_id or att.event_id.recurrent_id == 0:
|
||||
response = self.create_an_event(cr,uid,att.event_id,context=context)
|
||||
update_date = datetime.strptime(response['updated'],"%Y-%m-%dT%H:%M:%S.%fz")
|
||||
ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date':update_date})
|
||||
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date':update_date})
|
||||
cr.commit()
|
||||
response = self.create_an_event(cr, uid, att.event_id, context=context)
|
||||
update_date = datetime.strptime(response['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
|
||||
ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date': update_date})
|
||||
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date': update_date})
|
||||
cr.commit()
|
||||
|
||||
def bind_recurring_events_to_google(self, cr, uid, context):
|
||||
def bind_recurring_events_to_google(self, cr, uid, context):
|
||||
ev_obj = self.pool['calendar.event']
|
||||
att_obj = self.pool['calendar.attendee']
|
||||
user_obj = self.pool['res.users']
|
||||
myPartnerID = user_obj.browse(cr,uid,uid,context=context).partner_id.id
|
||||
myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
|
||||
|
||||
context_norecurrent = context.copy()
|
||||
context_norecurrent['virtual_id'] = False
|
||||
context_norecurrent['active_test'] = False
|
||||
|
||||
my_att_ids = att_obj.search(cr, uid,[('partner_id', '=', myPartnerID),('google_internal_event_id', '=', False)], context=context_norecurrent)
|
||||
|
||||
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
|
||||
my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('google_internal_event_id', '=', False)], context=context_norecurrent)
|
||||
|
||||
for att in att_obj.browse(cr, uid, my_att_ids, context=context):
|
||||
if att.event_id.recurrent_id and att.event_id.recurrent_id > 0:
|
||||
new_google_internal_event_id = False
|
||||
source_event_record = ev_obj.browse(cr, uid, att.event_id.recurrent_id, context)
|
||||
source_attendee_record_id = att_obj.search(cr, uid, [('partner_id','=', myPartnerID), ('event_id','=',source_event_record.id)], context=context)
|
||||
source_attendee_record_id = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('event_id', '=', source_event_record.id)], context=context)
|
||||
source_attendee_record = att_obj.browse(cr, uid, source_attendee_record_id, context)[0]
|
||||
|
||||
|
||||
if att.event_id.recurrent_id_date and source_event_record.allday and source_attendee_record.google_internal_event_id:
|
||||
new_google_internal_event_id = source_attendee_record.google_internal_event_id +'_'+ att.event_id.recurrent_id_date.split(' ')[0].replace('-','')
|
||||
new_google_internal_event_id = source_attendee_record.google_internal_event_id + '_' + att.event_id.recurrent_id_date.split(' ')[0].replace('-', '')
|
||||
elif att.event_id.recurrent_id_date and source_attendee_record.google_internal_event_id:
|
||||
new_google_internal_event_id = source_attendee_record.google_internal_event_id +'_'+ att.event_id.recurrent_id_date.replace('-','').replace(' ','T').replace(':','') + 'Z'
|
||||
new_google_internal_event_id = source_attendee_record.google_internal_event_id + '_' + att.event_id.recurrent_id_date.replace('-', '').replace(' ', 'T').replace(':', '') + 'Z'
|
||||
|
||||
if new_google_internal_event_id:
|
||||
#TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE !
|
||||
res = self.update_recurrent_event_exclu(cr, uid,new_google_internal_event_id, source_attendee_record.google_internal_event_id,att.event_id, context=context)
|
||||
self.update_recurrent_event_exclu(cr, uid, new_google_internal_event_id, source_attendee_record.google_internal_event_id, att.event_id, context=context)
|
||||
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context)
|
||||
cr.commit()
|
||||
|
||||
|
@ -523,16 +518,16 @@ class google_calendar(osv.AbstractModel):
|
|||
context_novirtual['active_test'] = False
|
||||
|
||||
all_event_from_google = self.get_event_dict(cr, uid, context=context)
|
||||
|
||||
|
||||
# Select all events from OpenERP which have been already synchronized in gmail
|
||||
my_att_ids = att_obj.search(cr, uid,[ ('partner_id', '=', myPartnerID),
|
||||
('google_internal_event_id', '!=', False),
|
||||
'|',
|
||||
('event_id.date_deadline','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
('event_id.end_date','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
], context=context_novirtual)
|
||||
my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID),
|
||||
('google_internal_event_id', '!=', False),
|
||||
'|',
|
||||
('event_id.date_deadline', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
('event_id.end_date', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
], context=context_novirtual)
|
||||
event_to_synchronize = {}
|
||||
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
|
||||
for att in att_obj.browse(cr, uid, my_att_ids, context=context):
|
||||
event = att.event_id
|
||||
|
||||
base_event_id = att.google_internal_event_id.split('_')[0]
|
||||
|
@ -544,7 +539,7 @@ class google_calendar(osv.AbstractModel):
|
|||
event_to_synchronize[base_event_id][att.google_internal_event_id] = SyncEvent()
|
||||
|
||||
ev_to_sync = event_to_synchronize[base_event_id][att.google_internal_event_id]
|
||||
|
||||
|
||||
ev_to_sync.OE.attendee_id = att.id
|
||||
ev_to_sync.OE.event = event
|
||||
ev_to_sync.OE.found = True
|
||||
|
@ -566,40 +561,35 @@ class google_calendar(osv.AbstractModel):
|
|||
event_to_synchronize[base_event_id][event_id] = SyncEvent()
|
||||
|
||||
ev_to_sync = event_to_synchronize[base_event_id][event_id]
|
||||
|
||||
|
||||
ev_to_sync.GG.event = event
|
||||
ev_to_sync.GG.found = True
|
||||
ev_to_sync.GG.isRecurrence = bool(event.get('recurrence',''))
|
||||
ev_to_sync.GG.isInstance = bool(event.get('recurringEventId',0))
|
||||
ev_to_sync.GG.update = event.get('updated',None) # if deleted, no date without browse event
|
||||
ev_to_sync.GG.isRecurrence = bool(event.get('recurrence', ''))
|
||||
ev_to_sync.GG.isInstance = bool(event.get('recurringEventId', 0))
|
||||
ev_to_sync.GG.update = event.get('updated', None) # if deleted, no date without browse event
|
||||
if ev_to_sync.GG.update:
|
||||
ev_to_sync.GG.update = ev_to_sync.GG.update.replace('T',' ').replace('Z','')
|
||||
ev_to_sync.GG.update = ev_to_sync.GG.update.replace('T', ' ').replace('Z', '')
|
||||
ev_to_sync.GG.status = (event.get('status') != 'cancelled')
|
||||
|
||||
######################
|
||||
|
||||
######################
|
||||
# PRE-PROCESSING #
|
||||
######################
|
||||
for base_event in event_to_synchronize:
|
||||
for current_event in event_to_synchronize[base_event]:
|
||||
event_to_synchronize[base_event][current_event].compute_OP()
|
||||
event_to_synchronize[base_event][current_event].compute_OP()
|
||||
#print event_to_synchronize[base_event]
|
||||
#print "========================================================"
|
||||
|
||||
|
||||
######################
|
||||
# DO ACTION #
|
||||
######################
|
||||
######################
|
||||
for base_event in event_to_synchronize:
|
||||
event_to_synchronize[base_event] = sorted(event_to_synchronize[base_event].iteritems(),key=operator.itemgetter(0))
|
||||
event_to_synchronize[base_event] = sorted(event_to_synchronize[base_event].iteritems(), key=operator.itemgetter(0))
|
||||
for current_event in event_to_synchronize[base_event]:
|
||||
cr.commit()
|
||||
event = current_event[1] # event is an Sync Event !
|
||||
|
||||
event = current_event[1] # event is an Sync Event !
|
||||
actToDo = event.OP
|
||||
actSrc = event.OP.src
|
||||
|
||||
# if not isinstance(actToDo, NothingToDo):
|
||||
# print event
|
||||
|
||||
context['curr_attendee'] = event.OE.attendee_id
|
||||
|
||||
if isinstance(actToDo, NothingToDo):
|
||||
|
@ -607,135 +597,134 @@ class google_calendar(osv.AbstractModel):
|
|||
elif isinstance(actToDo, Create):
|
||||
context_tmp = context.copy()
|
||||
context_tmp['NewMeeting'] = True
|
||||
if actSrc == 'GG':
|
||||
if actSrc == 'GG':
|
||||
res = self.update_from_google(cr, uid, False, event.GG.event, "create", context=context_tmp)
|
||||
event.OE.event_id = res
|
||||
meeting = calendar_event.browse(cr,uid,res,context=context)
|
||||
attendee_record_id = att_obj.search(cr, uid, [('partner_id','=', myPartnerID), ('event_id','=',res)], context=context)
|
||||
self.pool['calendar.attendee'].write(cr, uid, attendee_record_id, {'oe_synchro_date':meeting.oe_update_date, 'google_internal_event_id':event.GG.event['id']}, context=context_tmp)
|
||||
elif actSrc == 'OE':
|
||||
meeting = calendar_event.browse(cr, uid, res, context=context)
|
||||
attendee_record_id = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('event_id', '=', res)], context=context)
|
||||
self.pool['calendar.attendee'].write(cr, uid, attendee_record_id, {'oe_synchro_date': meeting.oe_update_date, 'google_internal_event_id': event.GG.event['id']}, context=context_tmp)
|
||||
elif actSrc == 'OE':
|
||||
raise "Should be never here, creation for OE is done before update !"
|
||||
#TODO Add to batch
|
||||
elif isinstance(actToDo, Update):
|
||||
if actSrc == 'GG':
|
||||
if actSrc == 'GG':
|
||||
self.update_from_google(cr, uid, event.OE.event, event.GG.event, 'write', context)
|
||||
elif actSrc == 'OE':
|
||||
elif actSrc == 'OE':
|
||||
self.update_to_google(cr, uid, event.OE.event, event.GG.event, context)
|
||||
elif isinstance(actToDo, Exclude):
|
||||
if actSrc == 'OE':
|
||||
self.delete_an_event(cr,uid,current_event[0],context=context)
|
||||
elif actSrc == 'GG':
|
||||
self.delete_an_event(cr, uid, current_event[0], context=context)
|
||||
elif actSrc == 'GG':
|
||||
new_google_event_id = event.GG.event['id'].split('_')[1]
|
||||
if 'T' in new_google_event_id:
|
||||
new_google_event_id = new_google_event_id.replace('T','')[:-1]
|
||||
new_google_event_id = new_google_event_id.replace('T', '')[:-1]
|
||||
else:
|
||||
new_google_event_id = new_google_event_id + "000000"
|
||||
|
||||
if event.GG.status:
|
||||
parent_event = {}
|
||||
parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id , new_google_event_id)
|
||||
parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id)
|
||||
res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)
|
||||
else:
|
||||
if event_to_synchronize[base_event][0][1].OE.event_id:
|
||||
parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id
|
||||
calendar_event.unlink(cr,uid,"%s-%s" % (parent_oe_id,new_google_event_id),unlink_level=1,context=context)
|
||||
parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id
|
||||
calendar_event.unlink(cr, uid, "%s-%s" % (parent_oe_id, new_google_event_id), unlink_level=1, context=context)
|
||||
|
||||
elif isinstance(actToDo, Delete):
|
||||
if actSrc == 'GG':
|
||||
self.delete_an_event(cr,uid,current_event[0],context=context)
|
||||
elif actSrc == 'OE':
|
||||
calendar_event.unlink(cr,uid,event.OE.event_id,unlink_level=0,context=context)
|
||||
self.delete_an_event(cr, uid, current_event[0], context=context)
|
||||
elif actSrc == 'OE':
|
||||
calendar_event.unlink(cr, uid, event.OE.event_id, unlink_level=0, context=context)
|
||||
return True
|
||||
|
||||
|
||||
def check_and_sync(self, cr, uid, oe_event, google_event, context):
|
||||
if datetime.strptime(oe_event.oe_update_date,"%Y-%m-%d %H:%M:%S.%f") > datetime.strptime(google_event['updated'],"%Y-%m-%dT%H:%M:%S.%fz"):
|
||||
if datetime.strptime(oe_event.oe_update_date, "%Y-%m-%d %H:%M:%S.%f") > datetime.strptime(google_event['updated'], "%Y-%m-%dT%H:%M:%S.%fz"):
|
||||
self.update_to_google(cr, uid, oe_event, google_event, context)
|
||||
elif datetime.strptime(oe_event.oe_update_date,"%Y-%m-%d %H:%M:%S.%f") < datetime.strptime(google_event['updated'],"%Y-%m-%dT%H:%M:%S.%fz"):
|
||||
elif datetime.strptime(oe_event.oe_update_date, "%Y-%m-%d %H:%M:%S.%f") < datetime.strptime(google_event['updated'], "%Y-%m-%dT%H:%M:%S.%fz"):
|
||||
self.update_from_google(cr, uid, oe_event, google_event, 'write', context)
|
||||
|
||||
def get_sequence(self,cr,uid,instance_id,context=None):
|
||||
def get_sequence(self, cr, uid, instance_id, context=None):
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
params = {
|
||||
'fields': 'sequence',
|
||||
'access_token' : self.get_token(cr,uid,context)
|
||||
}
|
||||
'fields': 'sequence',
|
||||
'access_token': self.get_token(cr, uid, context)
|
||||
}
|
||||
|
||||
headers = {'Content-type': 'application/json'}
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events/%s" % ('primary',instance_id)
|
||||
url = "/calendar/v3/calendars/%s/events/%s" % ('primary', instance_id)
|
||||
|
||||
content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
|
||||
return content.get('sequence',0)
|
||||
#################################
|
||||
return content.get('sequence', 0)
|
||||
#################################
|
||||
## MANAGE CONNEXION TO GMAIL ##
|
||||
#################################
|
||||
|
||||
def get_token(self,cr,uid,context=None):
|
||||
current_user = self.pool['res.users'].browse(cr,uid,uid,context=context)
|
||||
def get_token(self, cr, uid, context=None):
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
|
||||
if datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], "%Y-%m-%d %H:%M:%S") < (datetime.now() + timedelta(minutes=1)):
|
||||
self.do_refresh_token(cr,uid,context=context)
|
||||
self.do_refresh_token(cr, uid, context=context)
|
||||
current_user.refresh()
|
||||
|
||||
return current_user.google_calendar_token
|
||||
|
||||
def do_refresh_token(self,cr,uid,context=None):
|
||||
current_user = self.pool['res.users'].browse(cr,uid,uid,context=context)
|
||||
def do_refresh_token(self, cr, uid, context=None):
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
refresh = current_user.google_calendar_rtoken
|
||||
all_token = gs_pool._refresh_google_token_json(cr, uid, current_user.google_calendar_rtoken, self.STR_SERVICE, context=context)
|
||||
|
||||
vals = {}
|
||||
vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
|
||||
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
|
||||
|
||||
self.pool['res.users'].write(cr,SUPERUSER_ID,uid,vals,context=context)
|
||||
self.pool['res.users'].write(cr, SUPERUSER_ID, uid, vals, context=context)
|
||||
|
||||
def need_authorize(self,cr,uid,context=None):
|
||||
current_user = self.pool['res.users'].browse(cr,uid,uid,context=context)
|
||||
return current_user.google_calendar_rtoken == False
|
||||
def need_authorize(self, cr, uid, context=None):
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
return current_user.google_calendar_rtoken is False
|
||||
|
||||
def get_calendar_scope(self,RO=False):
|
||||
def get_calendar_scope(self, RO=False):
|
||||
readonly = RO and '.readonly' or ''
|
||||
return 'https://www.googleapis.com/auth/calendar%s' % (readonly)
|
||||
|
||||
def authorize_google_uri(self,cr,uid,from_url='http://www.openerp.com',context=None):
|
||||
url = self.pool['google.service']._get_authorize_uri(cr,uid,from_url,self.STR_SERVICE,scope=self.get_calendar_scope(),context=context)
|
||||
def authorize_google_uri(self, cr, uid, from_url='http://www.openerp.com', context=None):
|
||||
url = self.pool['google.service']._get_authorize_uri(cr, uid, from_url, self.STR_SERVICE, scope=self.get_calendar_scope(), context=context)
|
||||
return url
|
||||
|
||||
def can_authorize_google(self,cr,uid,context=None):
|
||||
def can_authorize_google(self, cr, uid, context=None):
|
||||
return self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager')
|
||||
|
||||
def set_all_tokens(self,cr,uid,authorization_code,context=None):
|
||||
def set_all_tokens(self, cr, uid, authorization_code, context=None):
|
||||
gs_pool = self.pool['google.service']
|
||||
all_token = gs_pool._get_google_token_json(cr, uid, authorization_code,self.STR_SERVICE,context=context)
|
||||
all_token = gs_pool._get_google_token_json(cr, uid, authorization_code, self.STR_SERVICE, context=context)
|
||||
|
||||
vals = {}
|
||||
vals['google_%s_rtoken' % self.STR_SERVICE] = all_token.get('refresh_token')
|
||||
vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
|
||||
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
|
||||
self.pool['res.users'].write(cr,SUPERUSER_ID,uid,vals,context=context)
|
||||
self.pool['res.users'].write(cr, SUPERUSER_ID, uid, vals, context=context)
|
||||
|
||||
def get_start_time_to_synchro(self, cr, uid, context=None) :
|
||||
def get_start_time_to_synchro(self, cr, uid, context=None):
|
||||
# WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
|
||||
number_of_week = 13
|
||||
return datetime.now()-timedelta(weeks=number_of_week)
|
||||
return datetime.now() - timedelta(weeks=number_of_week)
|
||||
|
||||
def get_need_synchro_attendee(self, cr, uid, context=None):
|
||||
# WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
|
||||
return True
|
||||
|
||||
class res_users(osv.Model):
|
||||
|
||||
class res_users(osv.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
_columns = {
|
||||
'google_calendar_rtoken': fields.char('Refresh Token'),
|
||||
'google_calendar_token': fields.char('User token'),
|
||||
'google_calendar_token': fields.char('User token'),
|
||||
'google_calendar_token_validity': fields.datetime('Token Validity'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class calendar_event(osv.Model):
|
||||
|
@ -743,7 +732,7 @@ class calendar_event(osv.Model):
|
|||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if context is None:
|
||||
context= {}
|
||||
context = {}
|
||||
sync_fields = set(['name', 'description', 'date', 'date_closed', 'date_deadline', 'attendee_ids', 'location', 'class'])
|
||||
if (set(vals.keys()) & sync_fields) and 'oe_update_date' not in vals.keys() and 'NewMeeting' not in context:
|
||||
vals['oe_update_date'] = datetime.now()
|
||||
|
@ -773,19 +762,17 @@ class calendar_attendee(osv.Model):
|
|||
'google_internal_event_id': fields.char('Google Calendar Event Id', size=256),
|
||||
'oe_synchro_date': fields.datetime('OpenERP Synchro Date'),
|
||||
}
|
||||
|
||||
_sql_constraints = [('google_id_uniq','unique(google_internal_event_id,partner_id,event_id)', 'Google ID should be unique!')]
|
||||
_sql_constraints = [('google_id_uniq', 'unique(google_internal_event_id,partner_id,event_id)', 'Google ID should be unique!')]
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
for id in ids:
|
||||
ref = vals.get('event_id',self.browse(cr,uid,id,context=context).event_id.id)
|
||||
ref = vals.get('event_id', self.browse(cr, uid, id, context=context).event_id.id)
|
||||
|
||||
# If attendees are updated, we need to specify that next synchro need an action
|
||||
# Except if it come from an update_from_google
|
||||
if not context.get('curr_attendee', False) and not context.get('NewMeeting', False):
|
||||
self.pool['calendar.event'].write(cr, uid, ref, {'oe_update_date':datetime.now()},context)
|
||||
|
||||
return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context)
|
||||
self.pool['calendar.event'].write(cr, uid, ref, {'oe_update_date': datetime.now()}, context)
|
||||
return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context)
|
||||
|
|
|
@ -370,7 +370,8 @@ class hr_holidays(osv.osv):
|
|||
'date': record.date_from,
|
||||
'end_date': record.date_to,
|
||||
'date_deadline': record.date_to,
|
||||
'state': 'open', # to block that meeting date in the calendar
|
||||
'state': 'open', # to block that meeting date in the calendar
|
||||
'class': 'confidential'
|
||||
}
|
||||
#Add the partner_id (if exist) as an attendee
|
||||
if record.user_id and record.user_id.partner_id:
|
||||
|
|
|
@ -160,8 +160,8 @@ class mail_mail(osv.Model):
|
|||
fragment['message_id'] = mail.mail_message_id.id
|
||||
elif mail.model and mail.res_id:
|
||||
fragment.update(model=mail.model, res_id=mail.res_id)
|
||||
|
||||
url = urljoin(base_url, "?%s#%s" % (urlencode(query), urlencode(fragment)))
|
||||
|
||||
url = urljoin(base_url, "/web?%s#%s" % (urlencode(query), urlencode(fragment)))
|
||||
return _("""<span class='oe_mail_footer_access'><small>Access your messages and documents <a style='color:inherit' href="%s">in OpenERP</a></small></span>""") % url
|
||||
else:
|
||||
return None
|
||||
|
|
|
@ -199,7 +199,7 @@ class mail_thread(osv.AbstractModel):
|
|||
], context=context)
|
||||
for fol in fol_obj.browse(cr, uid, fol_ids, context=context):
|
||||
thread_subtype_dict = res[fol.res_id]['message_subtype_data']
|
||||
for subtype in fol.subtype_ids:
|
||||
for subtype in [st for st in fol.subtype_ids if st.name in thread_subtype_dict]:
|
||||
thread_subtype_dict[subtype.name]['followed'] = True
|
||||
res[fol.res_id]['message_subtype_data'] = thread_subtype_dict
|
||||
|
||||
|
|
|
@ -514,13 +514,18 @@ class pos_order(osv.osv):
|
|||
_order = "id desc"
|
||||
|
||||
def create_from_ui(self, cr, uid, orders, context=None):
|
||||
#_logger.info("orders: %r", orders)
|
||||
|
||||
# Keep only new orders
|
||||
submitted_references = [o['data']['name'] for o in orders]
|
||||
existing_orders = self.search_read(cr, uid, domain=[('pos_reference', 'in', submitted_references)], fields=['pos_reference'], context=context)
|
||||
existing_references = set([o['pos_reference'] for o in existing_orders])
|
||||
orders_to_save = [o for o in orders if o['data']['name'] not in existing_references]
|
||||
|
||||
order_ids = []
|
||||
for tmp_order in orders:
|
||||
for tmp_order in orders_to_save:
|
||||
to_invoice = tmp_order['to_invoice']
|
||||
order = tmp_order['data']
|
||||
|
||||
|
||||
order_id = self.create(cr, uid, {
|
||||
'name': order['name'],
|
||||
'user_id': order['user_id'] or False,
|
||||
|
@ -542,7 +547,6 @@ class pos_order(osv.osv):
|
|||
if order['amount_return']:
|
||||
session = self.pool.get('pos.session').browse(cr, uid, order['pos_session_id'], context=context)
|
||||
cash_journal = session.cash_journal_id
|
||||
cash_statement = False
|
||||
if not cash_journal:
|
||||
cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', session.statement_ids)
|
||||
if not len(cash_journal_ids):
|
||||
|
@ -556,7 +560,11 @@ class pos_order(osv.osv):
|
|||
'journal': cash_journal.id,
|
||||
}, context=context)
|
||||
order_ids.append(order_id)
|
||||
self.signal_paid(cr, uid, [order_id])
|
||||
|
||||
try:
|
||||
self.signal_paid(cr, uid, [order_id])
|
||||
except Exception as e:
|
||||
_logger.error('Could not mark POS Order as Paid: %s', tools.ustr(e))
|
||||
|
||||
if to_invoice:
|
||||
self.action_invoice(cr, uid, [order_id], context)
|
||||
|
|
|
@ -672,7 +672,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
// returns true if the ean is a valid EAN codebar number by checking the control digit.
|
||||
// ean must be a string
|
||||
check_ean: function(ean){
|
||||
return this.ean_checksum(ean) === Number(ean[ean.length-1]);
|
||||
return /^\d+$/.test(ean) && this.ean_checksum(ean) === Number(ean[ean.length-1]);
|
||||
},
|
||||
// returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string.
|
||||
sanitize_ean:function(ean){
|
||||
|
@ -757,8 +757,13 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
scan: function(code){
|
||||
if(code.length < 3){
|
||||
return;
|
||||
}else if(code.length === 13 && /^\d+$/.test(code)){
|
||||
}else if(code.length === 13 && this.check_ean(code)){
|
||||
var parse_result = this.parse_ean(code);
|
||||
}else if(code.length === 12 && this.check_ean('0'+code)){
|
||||
// many barcode scanners strip the leading zero of ean13 barcodes.
|
||||
// This is because ean-13 are UCP-A with an additional zero at the beginning,
|
||||
// so by stripping zeros you get retrocompatibility with UCP-A systems.
|
||||
var parse_result = this.parse_ean('0'+code);
|
||||
}else if(this.pos.db.get_product_by_reference(code)){
|
||||
var parse_result = {
|
||||
encoding: 'reference',
|
||||
|
@ -767,12 +772,16 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
prefix: '',
|
||||
};
|
||||
}else{
|
||||
var parse_result = {
|
||||
encoding: 'error',
|
||||
type: 'error',
|
||||
code: code,
|
||||
prefix: '',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (parse_result.type === 'error') { //most likely a checksum error, raise warning
|
||||
console.warn('WARNING: barcode checksum error:',parse_result);
|
||||
}else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
|
||||
if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
|
||||
if(this.action_callback['product']){
|
||||
this.action_callback['product'](parse_result);
|
||||
}
|
||||
|
|
|
@ -421,62 +421,67 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
// it is therefore important to only call this method from inside a mutex
|
||||
// this method returns a deferred indicating wether the sending was successful or not
|
||||
// there is a timeout parameter which is set to 2 seconds by default.
|
||||
_flush_order: function(order_id, options){
|
||||
var self = this;
|
||||
options = options || {};
|
||||
timeout = typeof options.timeout === 'number' ? options.timeout : 7500;
|
||||
|
||||
this.set('synch',{state:'connecting', pending: this.get('synch').pending});
|
||||
|
||||
var order = this.db.get_order(order_id);
|
||||
order.to_invoice = options.to_invoice || false;
|
||||
|
||||
if(!order){
|
||||
// flushing a non existing order always fails
|
||||
return (new $.Deferred()).reject();
|
||||
}
|
||||
|
||||
// we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice,
|
||||
// then we want to notify the user that we are waiting on something )
|
||||
var rpc = (new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{shadow: !options.to_invoice, timeout:timeout});
|
||||
|
||||
rpc.fail(function(unused,event){
|
||||
// prevent an error popup creation by the rpc failure
|
||||
// we want the failure to be silent as we send the orders in the background
|
||||
event.preventDefault();
|
||||
console.error('Failed to send order:',order);
|
||||
});
|
||||
|
||||
rpc.done(function(){
|
||||
self.db.remove_order(order_id);
|
||||
var pending = self.db.get_orders().length;
|
||||
self.set('synch',{state: pending ? 'connecting' : 'connected', pending:pending});
|
||||
});
|
||||
|
||||
return rpc;
|
||||
_flush_order: function( order_id, options) {
|
||||
return this._flush_all_orders([this.db.get_order(order_id)], options);
|
||||
},
|
||||
|
||||
// attempts to send all the locally stored orders. As with _flush_order, it should only be
|
||||
// called from within a mutex.
|
||||
// this method returns a deferred that always succeeds when all orders have been tried to be sent,
|
||||
// even if none of them could actually be sent.
|
||||
_flush_all_orders: function(){
|
||||
_flush_all_orders: function () {
|
||||
var self = this;
|
||||
var orders = this.db.get_orders();
|
||||
var tried_all = new $.Deferred();
|
||||
self.set('synch', {
|
||||
state: 'connecting',
|
||||
pending: self.get('synch').pending
|
||||
});
|
||||
return self._save_to_server(self.db.get_orders()).done(function () {
|
||||
var pending = self.db.get_orders().length;
|
||||
self.set('synch', {
|
||||
state: pending ? 'connecting' : 'connected',
|
||||
pending: pending
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
function rec_flush(index){
|
||||
if(index < orders.length){
|
||||
self._flush_order(orders[index].id).always(function(){
|
||||
rec_flush(index+1);
|
||||
})
|
||||
}else{
|
||||
tried_all.resolve();
|
||||
}
|
||||
// send an array of orders to the server
|
||||
// available options:
|
||||
// - timeout: timeout for the rpc call in ms
|
||||
_save_to_server: function (orders, options) {
|
||||
if (!orders || !orders.length) {
|
||||
var result = $.Deferred();
|
||||
result.resolve();
|
||||
return result;
|
||||
}
|
||||
rec_flush(0);
|
||||
|
||||
options = options || {};
|
||||
|
||||
return tried_all;
|
||||
var self = this;
|
||||
var timeout = typeof options.timeout === 'number' ? options.timeout : 7500 * orders.length;
|
||||
|
||||
// we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice,
|
||||
// then we want to notify the user that we are waiting on something )
|
||||
var posOrderModel = new instance.web.Model('pos.order');
|
||||
return posOrderModel.call('create_from_ui',
|
||||
[_.map(orders, function (order) {
|
||||
order.to_invoice = options.to_invoice || false;
|
||||
return order;
|
||||
})],
|
||||
undefined,
|
||||
{
|
||||
shadow: !options.to_invoice,
|
||||
timeout: timeout
|
||||
}
|
||||
).then(function () {
|
||||
_.each(orders, function (order) {
|
||||
self.db.remove_order(order.id);
|
||||
});
|
||||
}).fail(function (unused, event){
|
||||
// prevent an error popup creation by the rpc failure
|
||||
// we want the failure to be silent as we send the orders in the background
|
||||
event.preventDefault();
|
||||
console.error('Failed to send orders:', orders);
|
||||
});
|
||||
},
|
||||
|
||||
scan_product: function(parsed_code){
|
||||
|
|
|
@ -548,15 +548,10 @@ class product_product(osv.osv):
|
|||
_product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',))
|
||||
|
||||
def _product_lst_price(self, cr, uid, ids, name, arg, context=None):
|
||||
res = dict.fromkeys(ids, 0.0)
|
||||
res = {}
|
||||
product_uom_obj = self.pool.get('product.uom')
|
||||
|
||||
# retrieve pricelist
|
||||
pricelist = None
|
||||
if context.get('pricelist'):
|
||||
pricelist = self.pool['product.pricelist'].browse(cr, uid, context.get('pricelist'), context=context)
|
||||
base_currency = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id
|
||||
|
||||
for id in ids:
|
||||
res.setdefault(id, 0.0)
|
||||
for product in self.browse(cr, uid, ids, context=context):
|
||||
if 'uom' in context:
|
||||
uom = product.uos_id or product.uom_id
|
||||
|
@ -564,11 +559,7 @@ class product_product(osv.osv):
|
|||
uom.id, product.list_price, context['uom'])
|
||||
else:
|
||||
res[product.id] = product.list_price
|
||||
res[product.id] = (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra
|
||||
# update the result, according to the eventual pricelist currency
|
||||
if pricelist and pricelist.currency_id:
|
||||
res[product.id] = self.pool['res.currency'].compute(
|
||||
cr, uid, base_currency.id, pricelist.currency_id.id, res[product.id], round=False, context=context)
|
||||
res[product.id] = (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra
|
||||
return res
|
||||
|
||||
def _save_product_lst_price(self, cr, uid, product_id, field_name, field_value, arg, context=None):
|
||||
|
|
|
@ -3,7 +3,7 @@ from openerp.osv import fields, osv
|
|||
|
||||
class website_config_settings(osv.osv_memory):
|
||||
_name = 'website.config.settings'
|
||||
_inherit = 'base.config.settings'
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
_columns = {
|
||||
'website_id': fields.many2one('website', string="website", required=True),
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
/*! http://mths.be/placeholder v2.0.7 by @mathias */
|
||||
;(function(window, document, $) {
|
||||
|
||||
// Opera Mini v7 doesn’t support placeholder although its DOM seems to indicate so
|
||||
var isOperaMini = Object.prototype.toString.call(window.operamini) == '[object OperaMini]';
|
||||
var isInputSupported = 'placeholder' in document.createElement('input') && !isOperaMini;
|
||||
var isTextareaSupported = 'placeholder' in document.createElement('textarea') && !isOperaMini;
|
||||
var prototype = $.fn;
|
||||
var valHooks = $.valHooks;
|
||||
var propHooks = $.propHooks;
|
||||
var hooks;
|
||||
var placeholder;
|
||||
|
||||
if (isInputSupported && isTextareaSupported) {
|
||||
|
||||
placeholder = prototype.placeholder = function() {
|
||||
return this;
|
||||
};
|
||||
|
||||
placeholder.input = placeholder.textarea = true;
|
||||
|
||||
} else {
|
||||
|
||||
placeholder = prototype.placeholder = function() {
|
||||
var $this = this;
|
||||
$this
|
||||
.filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]')
|
||||
.not('.placeholder')
|
||||
.bind({
|
||||
'focus.placeholder': clearPlaceholder,
|
||||
'blur.placeholder': setPlaceholder
|
||||
})
|
||||
.data('placeholder-enabled', true)
|
||||
.trigger('blur.placeholder');
|
||||
return $this;
|
||||
};
|
||||
|
||||
placeholder.input = isInputSupported;
|
||||
placeholder.textarea = isTextareaSupported;
|
||||
|
||||
hooks = {
|
||||
'get': function(element) {
|
||||
var $element = $(element);
|
||||
|
||||
var $passwordInput = $element.data('placeholder-password');
|
||||
if ($passwordInput) {
|
||||
return $passwordInput[0].value;
|
||||
}
|
||||
|
||||
return $element.data('placeholder-enabled') && $element.hasClass('placeholder') ? '' : element.value;
|
||||
},
|
||||
'set': function(element, value) {
|
||||
var $element = $(element);
|
||||
|
||||
var $passwordInput = $element.data('placeholder-password');
|
||||
if ($passwordInput) {
|
||||
return $passwordInput[0].value = value;
|
||||
}
|
||||
|
||||
if (!$element.data('placeholder-enabled')) {
|
||||
return element.value = value;
|
||||
}
|
||||
if (value == '') {
|
||||
element.value = value;
|
||||
// Issue #56: Setting the placeholder causes problems if the element continues to have focus.
|
||||
if (element != safeActiveElement()) {
|
||||
// We can't use `triggerHandler` here because of dummy text/password inputs :(
|
||||
setPlaceholder.call(element);
|
||||
}
|
||||
} else if ($element.hasClass('placeholder')) {
|
||||
clearPlaceholder.call(element, true, value) || (element.value = value);
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
// `set` can not return `undefined`; see http://jsapi.info/jquery/1.7.1/val#L2363
|
||||
return $element;
|
||||
}
|
||||
};
|
||||
|
||||
if (!isInputSupported) {
|
||||
valHooks.input = hooks;
|
||||
propHooks.value = hooks;
|
||||
}
|
||||
if (!isTextareaSupported) {
|
||||
valHooks.textarea = hooks;
|
||||
propHooks.value = hooks;
|
||||
}
|
||||
|
||||
$(function() {
|
||||
// Look for forms
|
||||
$(document).delegate('form', 'submit.placeholder', function() {
|
||||
// Clear the placeholder values so they don't get submitted
|
||||
var $inputs = $('.placeholder', this).each(clearPlaceholder);
|
||||
setTimeout(function() {
|
||||
$inputs.each(setPlaceholder);
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
|
||||
// Clear placeholder values upon page reload
|
||||
$(window).bind('beforeunload.placeholder', function() {
|
||||
$('.placeholder').each(function() {
|
||||
this.value = '';
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function args(elem) {
|
||||
// Return an object of element attributes
|
||||
var newAttrs = {};
|
||||
var rinlinejQuery = /^jQuery\d+$/;
|
||||
$.each(elem.attributes, function(i, attr) {
|
||||
if (attr.specified && !rinlinejQuery.test(attr.name)) {
|
||||
newAttrs[attr.name] = attr.value;
|
||||
}
|
||||
});
|
||||
return newAttrs;
|
||||
}
|
||||
|
||||
function clearPlaceholder(event, value) {
|
||||
var input = this;
|
||||
var $input = $(input);
|
||||
if (input.value == $input.attr('placeholder') && $input.hasClass('placeholder')) {
|
||||
if ($input.data('placeholder-password')) {
|
||||
$input = $input.hide().next().show().attr('id', $input.removeAttr('id').data('placeholder-id'));
|
||||
// If `clearPlaceholder` was called from `$.valHooks.input.set`
|
||||
if (event === true) {
|
||||
return $input[0].value = value;
|
||||
}
|
||||
$input.focus();
|
||||
} else {
|
||||
input.value = '';
|
||||
$input.removeClass('placeholder');
|
||||
input == safeActiveElement() && input.select();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setPlaceholder() {
|
||||
var $replacement;
|
||||
var input = this;
|
||||
var $input = $(input);
|
||||
var id = this.id;
|
||||
if (input.value == '') {
|
||||
if (input.type == 'password') {
|
||||
if (!$input.data('placeholder-textinput')) {
|
||||
try {
|
||||
$replacement = $input.clone().attr({ 'type': 'text' });
|
||||
} catch(e) {
|
||||
$replacement = $('<input>').attr($.extend(args(this), { 'type': 'text' }));
|
||||
}
|
||||
$replacement
|
||||
.removeAttr('name')
|
||||
.data({
|
||||
'placeholder-password': $input,
|
||||
'placeholder-id': id
|
||||
})
|
||||
.bind('focus.placeholder', clearPlaceholder);
|
||||
$input
|
||||
.data({
|
||||
'placeholder-textinput': $replacement,
|
||||
'placeholder-id': id
|
||||
})
|
||||
.before($replacement);
|
||||
}
|
||||
$input = $input.removeAttr('id').hide().prev().attr('id', id).show();
|
||||
// Note: `$input[0] != input` now!
|
||||
}
|
||||
$input.addClass('placeholder');
|
||||
$input[0].value = $input.attr('placeholder');
|
||||
} else {
|
||||
$input.removeClass('placeholder');
|
||||
}
|
||||
}
|
||||
|
||||
function safeActiveElement() {
|
||||
// Avoid IE9 `document.activeElement` of death
|
||||
// https://github.com/mathiasbynens/jquery-placeholder/pull/99
|
||||
try {
|
||||
return document.activeElement;
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
}(this, document, jQuery));
|
|
@ -26,6 +26,7 @@
|
|||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
-ms-filter: "alpha(opacity=50)";
|
||||
}
|
||||
|
||||
/* ---- OpenERP Style ---- {{{ */
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
background: transparent
|
||||
border: none
|
||||
+box-shadow(none)
|
||||
-ms-filter: "alpha(opacity=50)"
|
||||
|
||||
// }}}
|
||||
|
||||
|
|
|
@ -80,10 +80,8 @@
|
|||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
cursor: move;
|
||||
pointer-events: none;
|
||||
}
|
||||
.oe_snippet .oe_snippet_thumbnail {
|
||||
pointer-events: auto;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
|
@ -200,6 +198,7 @@
|
|||
|
||||
.oe_overlay {
|
||||
display: none;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
background: transparent;
|
||||
-webkit-border-radius: 3px;
|
||||
|
@ -214,20 +213,12 @@
|
|||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
.oe_overlay.oe_active {
|
||||
display: block;
|
||||
border-style: dashed;
|
||||
border-width: 1px;
|
||||
-webkit-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset;
|
||||
-moz-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset;
|
||||
box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset;
|
||||
border-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.oe_overlay .oe_handle {
|
||||
display: block !important;
|
||||
pointer-events: auto;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
@ -238,7 +229,18 @@
|
|||
height: 16px;
|
||||
margin: -2px;
|
||||
}
|
||||
.oe_overlay .oe_handle > div {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
border-style: dashed;
|
||||
border-width: 1px;
|
||||
border-color: #666666;
|
||||
-webkit-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.5), 0px 0px 0px 1px rgba(255, 255, 255, 0.5) inset;
|
||||
-moz-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.5), 0px 0px 0px 1px rgba(255, 255, 255, 0.5) inset;
|
||||
box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.5), 0px 0px 0px 1px rgba(255, 255, 255, 0.5) inset;
|
||||
}
|
||||
.oe_overlay .oe_handle.e:before, .oe_overlay .oe_handle.w:before, .oe_overlay .oe_handle.s:before, .oe_overlay .oe_handle.n:before, .oe_overlay .oe_handle.size .oe_handle_button {
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
@ -270,56 +272,70 @@
|
|||
-moz-box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
.oe_overlay .oe_handle.e, .oe_overlay .oe_handle.w {
|
||||
top: 4px;
|
||||
height: 100%;
|
||||
}
|
||||
.oe_overlay .oe_handle.e:before, .oe_overlay .oe_handle.w:before {
|
||||
content: "\f0d9-\f0da";
|
||||
line-height: 16px;
|
||||
}
|
||||
.oe_overlay .oe_handle.e > div, .oe_overlay .oe_handle.w > div {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
top: 2px;
|
||||
left: 8px;
|
||||
}
|
||||
.oe_overlay .oe_handle.e {
|
||||
left: auto;
|
||||
top: 2px;
|
||||
height: 100%;
|
||||
right: -6px;
|
||||
cursor: w-resize;
|
||||
}
|
||||
.oe_overlay .oe_handle.w {
|
||||
top: 2px;
|
||||
height: 100%;
|
||||
left: -6px;
|
||||
cursor: e-resize;
|
||||
}
|
||||
.oe_overlay .oe_handle.s, .oe_overlay .oe_handle.n {
|
||||
left: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
.oe_overlay .oe_handle.s:before, .oe_overlay .oe_handle.n:before {
|
||||
z-index: 0;
|
||||
content: "\f07d";
|
||||
text-align: center;
|
||||
padding: 1px;
|
||||
}
|
||||
.oe_overlay .oe_handle.s > div, .oe_overlay .oe_handle.n > div {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
top: 7px;
|
||||
left: 1px;
|
||||
}
|
||||
.oe_overlay .oe_handle.s {
|
||||
top: auto;
|
||||
left: 2px;
|
||||
width: 100%;
|
||||
bottom: -6px;
|
||||
cursor: n-resize;
|
||||
}
|
||||
.oe_overlay .oe_handle.n {
|
||||
left: 2px;
|
||||
width: 100%;
|
||||
top: -6px;
|
||||
cursor: s-resize;
|
||||
}
|
||||
.oe_overlay .oe_handle.n > div {
|
||||
top: 5px;
|
||||
}
|
||||
.oe_overlay .oe_handle.size {
|
||||
z-index: 3;
|
||||
top: auto;
|
||||
left: 2px;
|
||||
width: 100%;
|
||||
left: 50%;
|
||||
bottom: -6px;
|
||||
}
|
||||
.oe_overlay .oe_handle.size .oe_handle_button {
|
||||
z-index: 1;
|
||||
z-index: 3;
|
||||
content: "Resize";
|
||||
width: 64px;
|
||||
text-align: center;
|
||||
margin-left: -32px;
|
||||
margin-top: -10px;
|
||||
cursor: row-resize;
|
||||
left: 0px;
|
||||
top: 9px;
|
||||
}
|
||||
.oe_overlay .oe_handle.size .oe_handle_button:hover {
|
||||
background: rgba(30, 30, 30, 0.8);
|
||||
|
@ -328,25 +344,20 @@
|
|||
-moz-box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
.oe_overlay .oe_handle.size div {
|
||||
border-style: dashed;
|
||||
border-width: 0 0 1px 0;
|
||||
border-color: rgba(0, 0, 0, 0.5);
|
||||
position: relative;
|
||||
top: 8px;
|
||||
}
|
||||
.oe_overlay .icon.btn {
|
||||
display: inline-block;
|
||||
}
|
||||
.oe_overlay .oe_overlay_options {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 50% !important;
|
||||
text-align: center;
|
||||
top: -11px;
|
||||
z-index: 1002;
|
||||
}
|
||||
.oe_overlay .oe_overlay_options > .btn-group {
|
||||
left: -50%;
|
||||
}
|
||||
.oe_overlay .oe_overlay_options .btn, .oe_overlay .oe_overlay_options a {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
.oe_overlay .oe_overlay_options .dropdown {
|
||||
|
@ -505,7 +516,6 @@
|
|||
box-shadow: 0px 3px 17px rgba(99, 53, 150, 0.59);
|
||||
}
|
||||
.oe_snippet_editor .oe_snippet > * {
|
||||
pointer-events: none;
|
||||
margin-top: 16px;
|
||||
line-height: 1em;
|
||||
zoom: 0.6;
|
||||
|
|
|
@ -64,9 +64,7 @@
|
|||
overflow: hidden
|
||||
+user-select(none)
|
||||
cursor: move
|
||||
pointer-events: none
|
||||
.oe_snippet_thumbnail
|
||||
pointer-events: auto
|
||||
text-align: center
|
||||
height: 100%
|
||||
background: transparent
|
||||
|
@ -150,22 +148,17 @@
|
|||
|
||||
.oe_overlay
|
||||
display: none
|
||||
height: 0
|
||||
position: absolute
|
||||
background: transparent
|
||||
//@include background-image( repeating-linear-gradient(45deg, rgba(255,255,255,.02) ,rgba(255,255,255,.02) 35px, rgba(0,0,0,.02) 35px, rgba(0,0,0,.02) 75px))
|
||||
+border-radius(3px)
|
||||
@include transition(opacity 100ms linear)
|
||||
+box-sizing(border-box)
|
||||
pointer-events: none
|
||||
&.oe_active
|
||||
display: block
|
||||
border-style: dashed
|
||||
border-width: 1px
|
||||
+box-shadow(0px 0px 0px 1px rgba(255,255,255,0.3), 0px 0px 0px 1px rgba(255,255,255,0.3) inset)
|
||||
border-color: rgba(0, 0, 0, 0.5)
|
||||
.oe_handle
|
||||
display: block !important
|
||||
pointer-events: auto
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: 50%
|
||||
|
@ -173,7 +166,15 @@
|
|||
width: 16px
|
||||
height: 16px
|
||||
margin: -2px
|
||||
> div
|
||||
z-index: 1
|
||||
position: absolute
|
||||
border-style: dashed
|
||||
border-width: 1px
|
||||
border-color: #666666
|
||||
+box-shadow(0px 0px 0px 1px rgba(255,255,255,0.5), 0px 0px 0px 1px rgba(255,255,255,0.5) inset)
|
||||
&.e:before, &.w:before, &.s:before, &.n:before, &.size .oe_handle_button
|
||||
z-index: 2
|
||||
position: relative
|
||||
top: 50%
|
||||
left: 50%
|
||||
|
@ -196,72 +197,74 @@
|
|||
color: #fff
|
||||
+box-shadow(0 0 5px 3px rgba(255,255,255,.7))
|
||||
&.e, &.w
|
||||
top: 4px
|
||||
height: 100%
|
||||
&:before
|
||||
content: "\f0d9-\f0da"
|
||||
line-height: 16px
|
||||
> div
|
||||
width: 0
|
||||
height: 100%
|
||||
top: 2px
|
||||
left: 8px
|
||||
&.e
|
||||
left: auto
|
||||
top: 2px
|
||||
height: 100%
|
||||
right: -6px
|
||||
cursor: w-resize
|
||||
&.w
|
||||
top: 2px
|
||||
height: 100%
|
||||
left: -6px
|
||||
cursor: e-resize
|
||||
&.s, &.n
|
||||
left: 2px
|
||||
width: 100%
|
||||
&:before
|
||||
z-index: 0
|
||||
content: "\f07d"
|
||||
text-align: center
|
||||
padding: 1px
|
||||
> div
|
||||
width: 100%
|
||||
height: 0
|
||||
top: 7px
|
||||
left: 1px
|
||||
&.s
|
||||
top: auto
|
||||
left: 2px
|
||||
width: 100%
|
||||
bottom: -6px
|
||||
cursor: n-resize
|
||||
&.n
|
||||
left: 2px
|
||||
width: 100%
|
||||
top: -6px
|
||||
cursor: s-resize
|
||||
> div
|
||||
top: 5px
|
||||
&.size
|
||||
z-index: 3
|
||||
top: auto
|
||||
left: 2px
|
||||
width: 100%
|
||||
left: 50%
|
||||
bottom: -6px
|
||||
.oe_handle_button
|
||||
z-index: 1
|
||||
z-index: 3
|
||||
content: "Resize"
|
||||
width: 64px
|
||||
text-align: center
|
||||
margin-left: -32px
|
||||
margin-top: -10px
|
||||
cursor: row-resize
|
||||
left: 0px
|
||||
top: 9px
|
||||
&:hover
|
||||
background: rgba(30, 30, 30, .8)
|
||||
color: #fff
|
||||
+box-shadow(0 0 5px 3px rgba(255,255,255,.7))
|
||||
div
|
||||
border-style: dashed
|
||||
border-width: 0 0 1px 0
|
||||
border-color: rgba(0, 0, 0, 0.5)
|
||||
position: relative
|
||||
top: 8px
|
||||
|
||||
.icon.btn
|
||||
display: inline-block
|
||||
|
||||
.oe_overlay_options
|
||||
position: absolute
|
||||
width: 100%
|
||||
left: 50% !important
|
||||
text-align: center
|
||||
top: -11px
|
||||
z-index: 1002
|
||||
> .btn-group
|
||||
left: -50%
|
||||
.btn, a
|
||||
pointer-events: auto
|
||||
cursor: pointer
|
||||
.dropdown
|
||||
display: inline-block
|
||||
|
@ -392,7 +395,6 @@
|
|||
border: 2px solid rgb(151, 137, 255)
|
||||
box-shadow: 0px 3px 17px rgba(99, 53, 150, 0.59)
|
||||
& > *
|
||||
pointer-events: none
|
||||
margin-top: 16px
|
||||
line-height: 1em
|
||||
zoom: 0.6
|
||||
|
|
|
@ -39,12 +39,17 @@
|
|||
this.xml = text;
|
||||
},
|
||||
isWellFormed: function () {
|
||||
var error;
|
||||
if (document.implementation.createDocument) {
|
||||
var dom = new DOMParser().parseFromString(this.xml, "text/xml");
|
||||
var error = dom.getElementsByTagName("parsererror");
|
||||
return error.length === 0 || error;
|
||||
} else if (window.ActiveXObject) {
|
||||
// TODO test in IE
|
||||
// use try catch for ie
|
||||
try {
|
||||
var dom = new DOMParser().parseFromString(this.xml, "text/xml");
|
||||
error = dom.getElementsByTagName("parsererror");
|
||||
return error.length === 0 || $(error).text();
|
||||
} catch (e) {}
|
||||
}
|
||||
if (window.ActiveXObject) {
|
||||
// IE
|
||||
var msDom = new ActiveXObject("Microsoft.XMLDOM");
|
||||
msDom.async = false;
|
||||
msDom.loadXML(this.xml);
|
||||
|
@ -284,7 +289,7 @@
|
|||
def.reject("server", session, error);
|
||||
});
|
||||
} else {
|
||||
def.reject(null, session, $(isWellFormed).text());
|
||||
def.reject(null, session, isWellFormed);
|
||||
}
|
||||
return def;
|
||||
},
|
||||
|
|
|
@ -66,8 +66,10 @@
|
|||
// only enable editors manually
|
||||
CKEDITOR.disableAutoInline = true;
|
||||
// EDIT ALL THE THINGS
|
||||
CKEDITOR.dtd.$editable = $.extend(
|
||||
{}, CKEDITOR.dtd.$block, CKEDITOR.dtd.$inline);
|
||||
CKEDITOR.dtd.$editable = _.omit(
|
||||
$.extend({}, CKEDITOR.dtd.$block, CKEDITOR.dtd.$inline),
|
||||
// well maybe not *all* the things
|
||||
'ul', 'ol', 'li', 'table', 'tr', 'th', 'td');
|
||||
// Disable removal of empty elements on CKEDITOR activation. Empty
|
||||
// elements are used for e.g. support of FontAwesome icons
|
||||
CKEDITOR.dtd.$removeEmpty = {};
|
||||
|
@ -377,8 +379,7 @@
|
|||
});
|
||||
},
|
||||
upcast: function (el) {
|
||||
return el.attributes['class']
|
||||
&& (/\bfa\b/.test(el.attributes['class']));
|
||||
return el.hasClass('fa');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -650,6 +651,7 @@
|
|||
if (previous && previous === this) { return; }
|
||||
|
||||
var selected = new CKEDITOR.dom.element(this);
|
||||
// FIXME: fa nodes may not be editable widgets (?)
|
||||
if (!is_editable_node(selected) && !selected.hasClass('fa')) {
|
||||
return;
|
||||
}
|
||||
|
@ -837,7 +839,6 @@
|
|||
document.execCommand("enableInlineTableEditing", false, "false");
|
||||
} catch (e) {}
|
||||
|
||||
|
||||
// detect & setup any CKEDITOR widget within a newly dropped
|
||||
// snippet. There does not seem to be a simple way to do it for
|
||||
// HTML not inserted via ckeditor APIs:
|
||||
|
@ -1684,6 +1685,9 @@
|
|||
// ignore mutation if the *only* change is .cke_focus
|
||||
return change.length !== 1 || change[0] === 'cke_focus';
|
||||
case 'childList':
|
||||
setTimeout(function () {
|
||||
fixup_browser_crap(m.addedNodes);
|
||||
}, 0);
|
||||
// Remove ignorable nodes from addedNodes or removedNodes,
|
||||
// if either set remains non-empty it's considered to be an
|
||||
// impactful change. Otherwise it's ignored.
|
||||
|
@ -1721,4 +1725,69 @@
|
|||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
var programmatic_styles = {
|
||||
float: 1,
|
||||
display: 1,
|
||||
position: 1,
|
||||
top: 1,
|
||||
left: 1,
|
||||
right: 1,
|
||||
bottom: 1,
|
||||
};
|
||||
function fixup_browser_crap(nodes) {
|
||||
if (!nodes || !nodes.length) { return; }
|
||||
/**
|
||||
* Checks that the node only has a @style, not e.g. @class or whatever
|
||||
*/
|
||||
function has_only_style(node) {
|
||||
for (var i = 0; i < node.attributes.length; i++) {
|
||||
var attr = node.attributes[i];
|
||||
if (attr.attributeName !== 'style') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function has_programmatic_style(node) {
|
||||
for (var i = 0; i < node.style.length; i++) {
|
||||
var style = node.style[i];
|
||||
if (programmatic_styles[style]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i=0; i<nodes.length; ++i) {
|
||||
var node = nodes[i];
|
||||
if (node.nodeType !== document.ELEMENT_NODE) { continue; }
|
||||
|
||||
if (node.nodeName === 'SPAN'
|
||||
&& has_only_style(node)
|
||||
&& !has_programmatic_style(node)) {
|
||||
// On backspace, webkit browsers create a <span> with a bunch of
|
||||
// inline styles "remembering" where they come from. Refs:
|
||||
// http://www.neotericdesign.com/blog/2013/3/working-around-chrome-s-contenteditable-span-bug
|
||||
// https://code.google.com/p/chromium/issues/detail?id=226941
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=114791
|
||||
// http://dev.ckeditor.com/ticket/9998
|
||||
var child, parent = node.parentNode;
|
||||
while (child = node.firstChild) {
|
||||
parent.insertBefore(child, node);
|
||||
}
|
||||
parent.removeChild(node);
|
||||
// chances are we had e.g.
|
||||
// <p>foo</p>
|
||||
// <p>bar</p>
|
||||
// merged the lines getting this in webkit
|
||||
// <p>foo<span>bar</span></p>
|
||||
// after unwrapping the span, we have 2 text nodes
|
||||
// <p>[foo][bar]</p>
|
||||
// where we probably want only one. Normalize will merge
|
||||
// adjacent text nodes. However, does not merge text and cdata
|
||||
parent.normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
website.is_editable = website.is_editable || $('html').data('editable');
|
||||
website.is_editable_button= website.is_editable_button || $('html').data('editable');
|
||||
dom_ready.resolve();
|
||||
// fix for ie
|
||||
if($.fn.placeholder) $('input, textarea').placeholder();
|
||||
});
|
||||
|
||||
website.init_kanban = function ($kanban) {
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
'hidden.bs.modal': 'destroy'
|
||||
},
|
||||
start: function () {
|
||||
if (!window.location.origin) { // fix for ie9
|
||||
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '') + '/';
|
||||
}
|
||||
document.getElementById("mobile-viewport").src = window.location.origin + window.location.pathname + "#mobile-preview";
|
||||
this.$el.modal();
|
||||
},
|
||||
|
|
|
@ -190,13 +190,14 @@
|
|||
var mt = parseInt($target.css("margin-top") || 0);
|
||||
var mb = parseInt($target.css("margin-bottom") || 0);
|
||||
$el.css({
|
||||
'position': 'absolute',
|
||||
'width': $target.outerWidth(),
|
||||
'height': $target.outerHeight() + mt + mb+1,
|
||||
'top': pos.top - mt,
|
||||
'top': pos.top - mt - 5,
|
||||
'left': pos.left
|
||||
});
|
||||
$el.find(".oe_handle.size").css("bottom", (mb-7)+'px');
|
||||
$el.find(">.e,>.w").css({'height': $target.outerHeight() + mt + mb+1});
|
||||
$el.find(">.s").css({'top': $target.outerHeight() + mt + mb});
|
||||
$el.find(">.size").css({'top': $target.outerHeight() + mt});
|
||||
$el.find(">.s,>.n").css({'width': $target.outerWidth()-2});
|
||||
},
|
||||
show: function () {
|
||||
this.$el.removeClass("hidden");
|
||||
|
@ -213,14 +214,8 @@
|
|||
|
||||
bind_snippet_click_editor: function () {
|
||||
var self = this;
|
||||
var snipped_event_flag = false;
|
||||
$("#wrapwrap").on('click', function (event) {
|
||||
if (snipped_event_flag) {
|
||||
return;
|
||||
}
|
||||
snipped_event_flag = true;
|
||||
setTimeout(function () {snipped_event_flag = false;}, 0);
|
||||
var $target = $(event.srcElement);
|
||||
var $target = $(event.srcElement || event.target);
|
||||
if (!$target.attr("data-snippet-id")) {
|
||||
$target = $target.parents("[data-snippet-id]:first");
|
||||
}
|
||||
|
@ -262,6 +257,12 @@
|
|||
}
|
||||
if (this.$active_snipped_id) {
|
||||
this.snippet_blur(this.$active_snipped_id);
|
||||
var $overlay = this.$active_snipped_id.data("overlay");
|
||||
if ($overlay) {
|
||||
$overlay.remove();
|
||||
this.$active_snipped_id.removeData("overlay");
|
||||
}
|
||||
this.$active_snipped_id.removeData("snippet-editor");
|
||||
this.$active_snipped_id = false;
|
||||
}
|
||||
if ($snippet && $snippet.length) {
|
||||
|
@ -577,6 +578,20 @@
|
|||
var $target = $(this);
|
||||
if (!$target.data('overlay')) {
|
||||
var $zone = $(openerp.qweb.render('website.snippet_overlay'));
|
||||
|
||||
// fix for pointer-events: none with ie9
|
||||
if (document.body && document.body.addEventListener) {
|
||||
$zone.on("click mousedown mousedown", function passThrough(event) {
|
||||
event.preventDefault();
|
||||
$target.each(function() {
|
||||
// check if clicked point (taken from event) is inside element
|
||||
event.srcElement = this;
|
||||
$(this).trigger(event.type);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
$zone.appendTo('#oe_manipulators');
|
||||
$zone.data('target',$target);
|
||||
$target.data('overlay',$zone);
|
||||
|
@ -789,6 +804,7 @@
|
|||
this.parent = parent;
|
||||
this.$target = $(dom);
|
||||
this.$overlay = this.$target.data('overlay');
|
||||
this.$overlay.find('a[data-toggle="dropdown"]').dropdown();
|
||||
this.snippet_id = this.$target.data("snippet-id");
|
||||
this._readXMLData();
|
||||
this.load_style_options();
|
||||
|
|
|
@ -46,11 +46,11 @@
|
|||
<div t-name="website.snippets.resize" data-snippet-id='resize'>
|
||||
<!-- custom data for the widget -->
|
||||
<div class='oe_handles'>
|
||||
<div class='oe_handle n'></div>
|
||||
<div class='oe_handle e'></div>
|
||||
<div class='oe_handle w'></div>
|
||||
<div class='oe_handle size'><div class="oe_handle_button size">Resize</div><div class="oe_border"/></div>
|
||||
<div class='oe_handle s'></div>
|
||||
<div class='oe_handle n'><div></div></div>
|
||||
<div class='oe_handle e'><div></div></div>
|
||||
<div class='oe_handle w'><div></div></div>
|
||||
<div class='oe_handle size'><div class="oe_handle_button size">Resize</div></div>
|
||||
<div class='oe_handle s'><div></div></div>
|
||||
</div>
|
||||
<div class='oe_snippet_thumbnail'>Margin resize</div>
|
||||
</div>
|
||||
|
|
|
@ -272,6 +272,8 @@
|
|||
<script type="text/javascript" src='/website/static/lib/nearest/jquery.nearest.js'></script>
|
||||
<script type="text/javascript" src="/website/static/lib/MutationObservers/MutationObserver.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/website/static/lib/jquery.placeholder/jquery.placeholder.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/website/static/src/js/website.editor.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.editor.newpage.js" groups="base.group_website_designer"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.menu.js" groups="base.group_website_designer"></script>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
@charset "utf-8";
|
||||
@import url(compass/css3.css);
|
||||
@charset "UTF-8";
|
||||
.css_website_mail .has-error {
|
||||
border-color: red;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ class event_track_location(osv.osv):
|
|||
|
||||
class event_track(osv.osv):
|
||||
_name = "event.track"
|
||||
_description = 'Event Tracks'
|
||||
_order = 'priority, date'
|
||||
_inherit = ['mail.thread', 'ir.needaction_mixin', 'website.seo.metadata']
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="event_track_public" model="ir.rule">
|
||||
<field name="name">event tracks: Public</field>
|
||||
<field name="model_id" ref="website_event_track.model_event_track"/>
|
||||
<field name="domain_force">[('website_published', '=', True)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_public')), (4, ref('base.group_portal')), (4, ref('base.group_user'))]"/>
|
||||
<field name="groups" eval="[(4, ref('base.group_public')), (4, ref('base.group_portal'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
|
|
|
@ -197,7 +197,8 @@
|
|||
<h2 t-field="track.name" class="text-center"/>
|
||||
<h3 t-field="event.name" class="text-center text-muted"/>
|
||||
<ul t-if="track.tag_ids" class="text-center text-muted list-inline">
|
||||
<li t-foreach="track.tag_ids" t-as="tag_id" class="fa fa-tags">
|
||||
<li t-foreach="track.tag_ids" t-as="tag_id">
|
||||
<span class="fa fa-tags"></span>
|
||||
<a t-attf-href="/event/#{ slug(event) }/track/tag/#{ slug(tag_id) }">
|
||||
<span t-field="tag_id.name"/>
|
||||
</a>
|
||||
|
|
|
@ -7,7 +7,6 @@ from openerp.tools.translate import _
|
|||
from openerp.addons.web.http import request
|
||||
|
||||
class website_hr_recruitment(http.Controller):
|
||||
|
||||
@http.route([
|
||||
'/jobs',
|
||||
'/jobs/department/<model("hr.department"):department>',
|
||||
|
@ -61,27 +60,45 @@ class website_hr_recruitment(http.Controller):
|
|||
|
||||
@http.route(['/jobs/apply/<model("hr.job"):job>'], type='http', auth="public", website=True, multilang=True)
|
||||
def jobs_apply(self, job):
|
||||
return request.website.render("website_hr_recruitment.apply", { 'job': job })
|
||||
error = {}
|
||||
default = {}
|
||||
if 'website_hr_recruitment_error' in request.session:
|
||||
error = request.session.pop('website_hr_recruitment_error')
|
||||
default = request.session.pop('website_hr_recruitment_default')
|
||||
return request.website.render("website_hr_recruitment.apply", { 'job': job, 'error': error, 'default': default})
|
||||
|
||||
@http.route(['/jobs/thankyou'], methods=['POST'], type='http', auth="public", website=True, multilang=True)
|
||||
def jobs_thankyou(self, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
imd = request.registry['ir.model.data']
|
||||
|
||||
error = {}
|
||||
for field_name in ["partner_name", "phone", "email_from"]:
|
||||
if not post.get(field_name):
|
||||
error[field_name] = 'missing'
|
||||
if error:
|
||||
request.session['website_hr_recruitment_error'] = error
|
||||
ufile = post.pop('ufile')
|
||||
if ufile:
|
||||
error['ufile'] = 'reset'
|
||||
request.session['website_hr_recruitment_default'] = post
|
||||
return request.redirect('/jobs/apply/%s' % post.get("job_id"))
|
||||
|
||||
value = {
|
||||
'name': _('Online Form'),
|
||||
'user_id': False,
|
||||
'source_id' : imd.xmlid_to_res_id(cr, SUPERUSER_ID, 'hr_recruitment.source_website_company'),
|
||||
}
|
||||
for f in ['phone', 'email_from', 'partner_name', 'description', 'department_id', 'job_id']:
|
||||
for f in ['phone', 'email_from', 'partner_name', 'description']:
|
||||
value[f] = post.get(f)
|
||||
for f in ['department_id', 'job_id']:
|
||||
value[f] = int(post.get(f) or 0)
|
||||
|
||||
job_id = request.registry['hr.applicant'].create(cr, SUPERUSER_ID, value, context=context)
|
||||
applicant_id = request.registry['hr.applicant'].create(cr, SUPERUSER_ID, value, context=context)
|
||||
if post['ufile']:
|
||||
attachment_value = {
|
||||
'name': post['ufile'].filename,
|
||||
'res_name': value['partner_name'],
|
||||
'res_model': 'hr.applicant',
|
||||
'res_id': job_id,
|
||||
'res_id': applicant_id,
|
||||
'datas': base64.encodestring(post['ufile'].read()),
|
||||
'datas_fname': post['ufile'].filename,
|
||||
}
|
||||
|
|
|
@ -161,34 +161,35 @@
|
|||
<form class="form-horizontal mt32" action="/jobs/thankyou" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" t-att-value="job and job.department_id.id or False" name="department_id"/>
|
||||
<input type="hidden" t-att-value="job and job.id or False" name="job_id"/>
|
||||
<div class="form-group">
|
||||
<div t-attf-class="form-group #{error.get('partner_name') and 'has-error' or ''}">
|
||||
<label class="col-md-3 col-sm-4 control-label" for="partner_name">Your Name</label>
|
||||
<div class="col-md-7 col-sm-8">
|
||||
<input type="text" class="form-control" name="partner_name" required="True" />
|
||||
<input type="text" t-att-value="default.get('partner_name')" class="form-control" name="partner_name" required="True"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div t-attf-class="form-group #{error.get('email_from') and 'has-error' or ''}">
|
||||
<label class="col-md-3 col-sm-4 control-label" for="email_from">Your Email</label>
|
||||
<div class="col-md-7 col-sm-8">
|
||||
<input type="email" class="form-control" name="email_from" required="True" />
|
||||
<input type="email" t-att-value="default.get('email_from')" class="form-control" name="email_from" required="True"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div t-attf-class="form-group #{error.get('phone') and 'has-error' or ''}">
|
||||
<label class="col-md-3 col-sm-4 control-label" for="phone">Your Phone</label>
|
||||
<div class="col-md-7 col-sm-8">
|
||||
<input type="text" class="form-control" name="phone" required="True" />
|
||||
<input type="text" t-att-value="default.get('phone')" class="form-control" name="phone" required="True"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 col-sm-4 control-label" for="description">Short Introduction</label>
|
||||
<div class="col-md-7 col-sm-8">
|
||||
<textarea class="form-control" name="description" style="min-height: 120px"/>
|
||||
<textarea class="form-control" t-esc="default.get('description')" name="description" style="min-height: 120px"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 col-sm-4 control-label" for="ufile">Resume</label>
|
||||
<div class="col-md-7 col-sm-8">
|
||||
<input class="input-file" id="fileInput" type="file" name="ufile"/>
|
||||
<span t-if="error.get('ufile')">Please send again your resume.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
<h5><strong><a t-attf-href="/shop/product/{{ slug(product) }}/?{{ keep_query('search', 'filters', category=(category and int(category)), page=(pager['page']['num'] if pager['page']['num']>1 else None)) }}" t-field="product.name"/></strong></h5>
|
||||
<div class="product_price" t-if="product.product_variant_ids">
|
||||
<b>
|
||||
<t t-if="abs(product.product_variant_ids[0].lst_price - product.product_variant_ids[0].price) > 0.2">
|
||||
<t t-if="product.product_variant_ids[0].lst_price != product.product_variant_ids[0].price">
|
||||
<del class="text-danger"
|
||||
t-field="product.product_variant_ids[0].lst_price" t-field-options='{
|
||||
"widget": "monetary",
|
||||
|
@ -231,7 +231,7 @@
|
|||
<xpath expr="//div[@class='product_price']" position="inside">
|
||||
<form action="/shop/add_cart/" method="post" style="display: inline-block;">
|
||||
<input name="product_id" t-att-value="product.product_variant_ids[0].id" type="hidden"/>
|
||||
<button type="submit" class="fa fa-shopping-cart"/>
|
||||
<button type="submit" class="btn btn-default btn-xs fa fa-shopping-cart"/>
|
||||
</form>
|
||||
</xpath>
|
||||
</template>
|
||||
|
|
Loading…
Reference in New Issue