[MERGE] trunk dev addons1
bzr revid: tfr@openerp.com-20101222132330-537grpixmbdtz43s
This commit is contained in:
commit
b845bb4ee1
|
@ -708,6 +708,7 @@ class account_journal(osv.osv):
|
|||
|
||||
return self.name_get(cr, user, ids, context=context)
|
||||
|
||||
|
||||
def onchange_type(self, cr, uid, ids, type, currency, context=None):
|
||||
obj_data = self.pool.get('ir.model.data')
|
||||
user_pool = self.pool.get('res.users')
|
||||
|
|
|
@ -1313,7 +1313,6 @@ class account_invoice_line(osv.osv):
|
|||
if context is None:
|
||||
context = {}
|
||||
company_id = context.get('company_id',False)
|
||||
tax_obj = self.pool.get('account.tax')
|
||||
if not partner_id:
|
||||
raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
|
||||
if not product:
|
||||
|
|
|
@ -65,8 +65,6 @@ class crossovered_analytic(report_sxw.rml_parse):
|
|||
def _ref_lines(self,form):
|
||||
result = []
|
||||
res = {}
|
||||
acc_id = []
|
||||
final = []
|
||||
acc_pool = self.pool.get('account.analytic.account')
|
||||
line_pool = self.pool.get('account.analytic.line')
|
||||
|
||||
|
@ -128,7 +126,6 @@ class crossovered_analytic(report_sxw.rml_parse):
|
|||
line_pool = self.pool.get('account.analytic.line')
|
||||
acc_id = []
|
||||
final = []
|
||||
child_ids = []
|
||||
self.list_ids = []
|
||||
|
||||
self.final_list = self.find_children(ids)
|
||||
|
|
|
@ -106,14 +106,16 @@ class audittrail_rule(osv.osv):
|
|||
"""
|
||||
obj_action = self.pool.get('ir.actions.act_window')
|
||||
val_obj = self.pool.get('ir.values')
|
||||
value=''
|
||||
#start Loop
|
||||
for thisrule in self.browse(cr, uid, ids):
|
||||
if thisrule.id in self.__functions:
|
||||
for function in self.__functions[thisrule.id]:
|
||||
setattr(function[0], function[1], function[2])
|
||||
w_id = obj_action.search(cr, uid, [('name', '=', 'View Log'), ('res_model', '=', 'audittrail.log'), ('src_model', '=', thisrule.object_id.model)])
|
||||
obj_action.unlink(cr, uid, w_id)
|
||||
value = "ir.actions.act_window" + ',' + str(w_id[0])
|
||||
if w_id:
|
||||
obj_action.unlink(cr, uid, w_id)
|
||||
value = "ir.actions.act_window" + ',' + str(w_id[0])
|
||||
val_id = val_obj.search(cr, uid, [('model', '=', thisrule.object_id.model), ('value', '=', value)])
|
||||
if val_id:
|
||||
res = ir.ir_del(cr, uid, val_id[0])
|
||||
|
|
|
@ -29,6 +29,11 @@ import re
|
|||
import time
|
||||
import tools
|
||||
|
||||
|
||||
def get_datetime(date_field):
|
||||
return datetime.strptime(date_field[:19], '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
class base_action_rule(osv.osv):
|
||||
""" Base Action Rules """
|
||||
|
||||
|
@ -115,6 +120,7 @@ the rule to mark CC(mail to any other person defined in actions)."),
|
|||
help="Use a python expression to specify the right field on which one than we will use for the 'From' field of the header"),
|
||||
'act_email_to' : fields.char('Email To', size=64, required=False,
|
||||
help="Use a python expression to specify the right field on which one than we will use for the 'To' field of the header"),
|
||||
'last_run': fields.datetime('Last Run', readonly=1),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
|
@ -145,7 +151,7 @@ the rule to mark CC(mail to any other person defined in actions)."),
|
|||
# Searching for action rules
|
||||
cr.execute("SELECT model.model, rule.id FROM base_action_rule rule \
|
||||
LEFT JOIN ir_model model on (model.id = rule.model_id) \
|
||||
where active")
|
||||
WHERE active")
|
||||
res = cr.fetchall()
|
||||
# Check if any rule matching with current object
|
||||
for obj_name, rule_id in res:
|
||||
|
@ -153,7 +159,10 @@ the rule to mark CC(mail to any other person defined in actions)."),
|
|||
continue
|
||||
else:
|
||||
obj = self.pool.get(obj_name)
|
||||
self._action(cr, uid, [rule_id], obj.browse(cr, uid, ids, context=context), context=context)
|
||||
# If the rule doesn't involve a time condition, run it immediately
|
||||
# Otherwise we let the scheduler run the action
|
||||
if self.browse(cr, uid, rule_id, context=context).trg_date_type == 'none':
|
||||
self._action(cr, uid, [rule_id], obj.browse(cr, uid, ids, context=context), context=context)
|
||||
return True
|
||||
|
||||
def _create(self, old_create, model, context=None):
|
||||
|
@ -206,8 +215,50 @@ the rule to mark CC(mail to any other person defined in actions)."),
|
|||
"""
|
||||
rule_pool = self.pool.get('base.action.rule')
|
||||
rule_ids = rule_pool.search(cr, uid, [], context=context)
|
||||
return self._register_hook(cr, uid, rule_ids, context=context)
|
||||
|
||||
self._register_hook(cr, uid, rule_ids, context=context)
|
||||
|
||||
rules = self.browse(cr, uid, rule_ids, context=context)
|
||||
for rule in rules:
|
||||
model = rule.model_id.model
|
||||
model_pool = self.pool.get(model)
|
||||
last_run = False
|
||||
if rule.last_run:
|
||||
last_run = get_datetime(rule.last_run)
|
||||
now = datetime.now()
|
||||
for obj_id in model_pool.search(cr, uid, [], context=context):
|
||||
obj = model_pool.browse(cr, uid, obj_id, context=context)
|
||||
# Calculate when this action should next occur for this object
|
||||
base = False
|
||||
if rule.trg_date_type=='create' and hasattr(obj, 'create_date'):
|
||||
base = obj.create_date
|
||||
elif (rule.trg_date_type=='action_last'
|
||||
and hasattr(obj, 'create_date')):
|
||||
if hasattr(obj, 'date_action_last') and obj.date_action_last:
|
||||
base = obj.date_action_last
|
||||
else:
|
||||
base = obj.create_date
|
||||
elif (rule.trg_date_type=='deadline'
|
||||
and hasattr(obj, 'date_deadline')
|
||||
and obj.date_deadline):
|
||||
base = obj.date_deadline
|
||||
elif (rule.trg_date_type=='date'
|
||||
and hasattr(obj, 'date')
|
||||
and obj.date):
|
||||
base = obj.date
|
||||
if base:
|
||||
fnct = {
|
||||
'minutes': lambda interval: timedelta(minutes=interval),
|
||||
'day': lambda interval: timedelta(days=interval),
|
||||
'hour': lambda interval: timedelta(hours=interval),
|
||||
'month': lambda interval: timedelta(months=interval),
|
||||
}
|
||||
base = get_datetime(base)
|
||||
delay = fnct[rule.trg_date_range_type](rule.trg_date_range)
|
||||
action_date = base + delay
|
||||
if (not last_run or (last_run <= action_date < now)):
|
||||
self._action(cr, uid, [rule.id], [obj], context=context)
|
||||
rule_pool.write(cr, uid, [rule.id], {'last_run': now},
|
||||
context=context)
|
||||
|
||||
def format_body(self, body):
|
||||
""" Foramat Action rule's body
|
||||
|
@ -391,7 +442,6 @@ the rule to mark CC(mail to any other person defined in actions)."),
|
|||
if context is None:
|
||||
context = {}
|
||||
|
||||
|
||||
context.update({'action': True})
|
||||
if not scrit:
|
||||
scrit = []
|
||||
|
@ -403,41 +453,6 @@ the rule to mark CC(mail to any other person defined in actions)."),
|
|||
if not ok:
|
||||
continue
|
||||
|
||||
base = False
|
||||
if action.trg_date_type=='create' and hasattr(obj, 'create_date'):
|
||||
base = datetime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S')
|
||||
elif action.trg_date_type=='action_last' and hasattr(obj, 'create_date'):
|
||||
if hasattr(obj, 'date_action_last') and obj.date_action_last:
|
||||
base = datetime.strptime(obj.date_action_last, '%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
base = datetime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S')
|
||||
elif action.trg_date_type=='deadline' and hasattr(obj, 'date_deadline') \
|
||||
and obj.date_deadline:
|
||||
base = datetime.strptime(obj.date_deadline, '%Y-%m-%d %H:%M:%S')
|
||||
elif action.trg_date_type=='date' and hasattr(obj, 'date') and obj.date:
|
||||
base = datetime.strptime(obj.date, '%Y-%m-%d %H:%M:%S')
|
||||
if base:
|
||||
fnct = {
|
||||
'minutes': lambda interval: timedelta(minutes=interval),
|
||||
'day': lambda interval: timedelta(days=interval),
|
||||
'hour': lambda interval: timedelta(hours=interval),
|
||||
'month': lambda interval: timedelta(months=interval),
|
||||
}
|
||||
d = base + fnct[action.trg_date_range_type](action.trg_date_range)
|
||||
dt = d.strftime('%Y-%m-%d %H:%M:%S')
|
||||
ok = False
|
||||
if hasattr(obj, 'date_action_last') and hasattr(obj, 'date_action_next'):
|
||||
ok = (dt <= time.strftime('%Y-%m-%d %H:%M:%S')) and \
|
||||
((not obj.date_action_next) or \
|
||||
(dt >= obj.date_action_next and \
|
||||
obj.date_action_last < obj.date_action_next))
|
||||
if not ok:
|
||||
if not obj.date_action_next or dt < obj.date_action_next:
|
||||
obj.date_action_next = dt
|
||||
model_obj.write(cr, uid, [obj.id], {'date_action_next': dt}, context)
|
||||
else:
|
||||
ok = action.trg_date_type == 'none'
|
||||
|
||||
if ok:
|
||||
self.do_action(cr, uid, action, model_obj, obj, context)
|
||||
break
|
||||
|
|
|
@ -1024,45 +1024,43 @@ class calendar_event(osv.osv):
|
|||
|
||||
qry = "UPDATE \"%s\" set rrule_type=%%s " % self._table
|
||||
qry_args = [ rrule_type, ]
|
||||
new_val = val.copy()
|
||||
for k, v in val.items():
|
||||
if val['freq'] == 'weekly' and val.get('byday'):
|
||||
for day in val['byday'].split(','):
|
||||
new_val[day] = True
|
||||
val.pop('byday')
|
||||
|
||||
if rrule_type == 'custom':
|
||||
new_val = val.copy()
|
||||
for k, v in val.items():
|
||||
if val['freq'] == 'weekly' and val.get('byday'):
|
||||
for day in val['byday'].split(','):
|
||||
new_val[day] = True
|
||||
val.pop('byday')
|
||||
if val.get('until'):
|
||||
until = parser.parse(''.join((re.compile('\d')).findall(val.get('until'))))
|
||||
new_val['end_date'] = until.strftime('%Y-%m-%d')
|
||||
val.pop('until')
|
||||
new_val.pop('until')
|
||||
|
||||
if val.get('until'):
|
||||
until = parser.parse(''.join((re.compile('\d')).findall(val.get('until'))))
|
||||
new_val['end_date'] = until.strftime('%Y-%m-%d')
|
||||
val.pop('until')
|
||||
new_val.pop('until')
|
||||
if val.get('bymonthday'):
|
||||
new_val['day'] = val.get('bymonthday')
|
||||
val.pop('bymonthday')
|
||||
new_val['select1'] = 'date'
|
||||
new_val.pop('bymonthday')
|
||||
|
||||
if val.get('bymonthday'):
|
||||
new_val['day'] = val.get('bymonthday')
|
||||
val.pop('bymonthday')
|
||||
new_val['select1'] = 'date'
|
||||
new_val.pop('bymonthday')
|
||||
if val.get('byday'):
|
||||
d = val.get('byday')
|
||||
if '-' in d:
|
||||
new_val['byday'] = d[:2]
|
||||
new_val['week_list'] = d[2:4].upper()
|
||||
else:
|
||||
new_val['byday'] = d[:1]
|
||||
new_val['week_list'] = d[1:3].upper()
|
||||
new_val['select1'] = 'day'
|
||||
|
||||
if val.get('byday'):
|
||||
d = val.get('byday')
|
||||
if '-' in d:
|
||||
new_val['byday'] = d[:2]
|
||||
new_val['week_list'] = d[2:4].upper()
|
||||
else:
|
||||
new_val['byday'] = d[:1]
|
||||
new_val['week_list'] = d[1:3].upper()
|
||||
new_val['select1'] = 'day'
|
||||
if val.get('bymonth'):
|
||||
new_val['month_list'] = val.get('bymonth')
|
||||
val.pop('bymonth')
|
||||
new_val.pop('bymonth')
|
||||
|
||||
if val.get('bymonth'):
|
||||
new_val['month_list'] = val.get('bymonth')
|
||||
val.pop('bymonth')
|
||||
new_val.pop('bymonth')
|
||||
|
||||
for k, v in new_val.items():
|
||||
qry += ", %s=%%s" % k
|
||||
qry_args.append(v)
|
||||
for k, v in new_val.items():
|
||||
qry += ", %s=%%s" % k
|
||||
qry_args.append(v)
|
||||
|
||||
qry = qry + " where id=%s"
|
||||
qry_args.append(id)
|
||||
|
@ -1082,10 +1080,12 @@ class calendar_event(osv.osv):
|
|||
for datas in self.read(cr, uid, ids, context=context):
|
||||
event = datas['id']
|
||||
if datas.get('rrule_type'):
|
||||
if datas['rrule_type']=='daily_working':
|
||||
datas.update({'rrule_type': 'weekly'})
|
||||
if datas.get('rrule_type') == 'none':
|
||||
result[event] = False
|
||||
cr.execute("UPDATE %s set exrule=Null where id=%%s" % self._table,( event,))
|
||||
elif datas.get('rrule_type') == 'custom':
|
||||
if datas.get('rrule_type') :
|
||||
if datas.get('interval', 0) < 0:
|
||||
raise osv.except_osv(_('Warning!'), _('Interval can not be Negative'))
|
||||
if datas.get('count', 0) < 0:
|
||||
|
@ -1132,7 +1132,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
|
|||
FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU'),
|
||||
'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
|
||||
('weekly', 'Weekly'), ('monthly', 'Monthly'), \
|
||||
('yearly', 'Yearly'), ('custom', 'Custom')],
|
||||
('yearly', 'Yearly'),],
|
||||
'Recurrency', states={'done': [('readonly', True)]},
|
||||
help="Let the event automatically repeat at that interval"),
|
||||
'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
|
||||
|
@ -1144,16 +1144,15 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
|
|||
'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
|
||||
'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
|
||||
'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
|
||||
'freq': fields.selection([('None', 'No Repeat'), \
|
||||
('hourly', 'Hours'), \
|
||||
('daily', 'Days'), \
|
||||
('weekly', 'Weeks'), \
|
||||
('monthly', 'Months'), \
|
||||
('yearly', 'Years'), \
|
||||
('secondly', 'Seconds'), \
|
||||
('minutely', 'Minutes') ], 'Frequency'),
|
||||
'interval': fields.integer('Interval', help="Repeat every x"),
|
||||
'count': fields.integer('Count', help="Repeat max that times"),
|
||||
'freq': fields.selection([('None', 'No Repeat'),
|
||||
('hourly', 'Hours'),
|
||||
('daily', 'Days'),
|
||||
('weekly', 'Weeks'),
|
||||
('monthly', 'Months'),
|
||||
('yearly', 'Years'), ], 'Frequency'),
|
||||
|
||||
'interval': fields.integer('Interval', help="Repeat every (Days/Week/Month/Year)"),
|
||||
'count': fields.integer('Repeat', help="Repeat x times"),
|
||||
'mo': fields.boolean('Mon'),
|
||||
'tu': fields.boolean('Tue'),
|
||||
'we': fields.boolean('Wed'),
|
||||
|
@ -1161,7 +1160,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
|
|||
'fr': fields.boolean('Fri'),
|
||||
'sa': fields.boolean('Sat'),
|
||||
'su': fields.boolean('Sun'),
|
||||
'select1': fields.selection([('date', 'Date of month'), \
|
||||
'select1': fields.selection([('date', 'Date of month'),
|
||||
('day', 'Day of month')], 'Option'),
|
||||
'day': fields.integer('Date of month'),
|
||||
'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
|
||||
|
@ -1177,7 +1176,9 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
|
|||
'event_id', 'attendee_id', 'Attendees'),
|
||||
'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
|
||||
'active': fields.boolean('Active', help="If the active field is set to \
|
||||
true, it will allow you to hide the event alarm information without removing it.")
|
||||
true, it will allow you to hide the event alarm information without removing it."),
|
||||
'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
|
||||
'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."),
|
||||
}
|
||||
def default_organizer(self, cr, uid, context=None):
|
||||
user_pool = self.pool.get('res.users')
|
||||
|
@ -1199,45 +1200,15 @@ true, it will allow you to hide the event alarm information without removing it.
|
|||
'organizer': default_organizer,
|
||||
}
|
||||
|
||||
def open_event(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Open Event From for Editing
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of event’s IDs
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: Dictionary value which open Crm Meeting form.
|
||||
"""
|
||||
if context is None:
|
||||
def onchange_edit_all(self, cr, uid, ids, rrule_type,edit_all, context=None):
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
data_obj = self.pool.get('ir.model.data')
|
||||
|
||||
value = {}
|
||||
|
||||
id2 = data_obj._get_id(cr, uid, 'base_calendar', 'event_form_view')
|
||||
id3 = data_obj._get_id(cr, uid, 'base_calendar', 'event_tree_view')
|
||||
id4 = data_obj._get_id(cr, uid, 'base_calendar', 'event_calendar_view')
|
||||
if id2:
|
||||
id2 = data_obj.browse(cr, uid, id2, context=context).res_id
|
||||
if id3:
|
||||
id3 = data_obj.browse(cr, uid, id3, context=context).res_id
|
||||
if id4:
|
||||
id4 = data_obj.browse(cr, uid, id4, context=context).res_id
|
||||
for id in ids:
|
||||
value = {
|
||||
'name': _('Event'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form,tree',
|
||||
'res_model': 'calendar.event',
|
||||
'view_id': False,
|
||||
'views': [(id2, 'form'), (id3, 'tree'), (id4, 'calendar')],
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_id': base_calendar_id2real_id(id),
|
||||
'nodestroy': True
|
||||
}
|
||||
|
||||
return value
|
||||
if edit_all and rrule_type:
|
||||
for id in ids:
|
||||
base_calendar_id2real_id(id)
|
||||
return value
|
||||
|
||||
def modify_all(self, cr, uid, event_ids, defaults, context=None, *args):
|
||||
"""
|
||||
|
@ -1367,15 +1338,13 @@ true, it will allow you to hide the event alarm information without removing it.
|
|||
weekstring = ''
|
||||
monthstring = ''
|
||||
yearstring = ''
|
||||
|
||||
freq = datas.get('freq')
|
||||
if freq == 'None':
|
||||
freq=datas.get('rrule_type')
|
||||
if freq == 'none':
|
||||
return ''
|
||||
|
||||
|
||||
interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
|
||||
|
||||
if freq == 'weekly':
|
||||
|
||||
byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
|
||||
if byday:
|
||||
weekstring = ';BYDAY=' + ','.join(byday)
|
||||
|
@ -1388,16 +1357,7 @@ true, it will allow you to hide the event alarm information without removing it.
|
|||
elif datas.get('select1')=='date':
|
||||
monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
|
||||
|
||||
elif freq == 'yearly':
|
||||
if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
|
||||
raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
|
||||
bymonth = ';BYMONTH=' + str(datas.get('month_list'))
|
||||
if datas.get('select1')=='day':
|
||||
bystring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
|
||||
elif datas.get('select1')=='date':
|
||||
bystring = ';BYMONTHDAY=' + str(datas.get('day'))
|
||||
yearstring = bymonth + bystring
|
||||
|
||||
|
||||
if datas.get('end_date'):
|
||||
datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
|
||||
enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
|
||||
|
|
|
@ -241,62 +241,9 @@
|
|||
<field name="alarm_id" string="Reminder"
|
||||
widget="selection" />
|
||||
<group colspan="2" col="4" >
|
||||
<field name="rrule_type" string="Recurrency"
|
||||
colspan="1" attrs="{'readonly':[('recurrent_uid','!=',False)]}"/>
|
||||
<button string="Edit All"
|
||||
help="Edit all Ourrences of recurrent Events"
|
||||
attrs="{'invisible':[('rrule_type','in', ('none', False))]}"
|
||||
name="open_event" icon="gtk-edit"
|
||||
type="object" />
|
||||
<button string="Exclude range" groups="base.group_extended"
|
||||
help="Add Exception Rule"
|
||||
name="%(base_calendar.action_base_calendar_set_exrule)d" icon="gtk-zoom-out" type="action"
|
||||
context="{'model' : 'calendar.event'}"
|
||||
attrs="{'invisible':[('rrule_type','in', ('none', False))]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group col="4" colspan="4" name="rrule" attrs="{'invisible': [('rrule_type','!=','custom')]}">
|
||||
<separator string="Select data for Custom Rule" colspan="8"/>
|
||||
<group col="8" colspan="4">
|
||||
<field name="interval" />
|
||||
<field name="freq" />
|
||||
<field name="count" />
|
||||
<field name="end_date" />
|
||||
</group>
|
||||
<group col="14" colspan="4" name="Select weekdays"
|
||||
attrs="{'invisible' : [('freq','!=','weekly')]}">
|
||||
<field name="mo" colspan="1" />
|
||||
<field name="tu" colspan="1" />
|
||||
<field name="we" colspan="1" />
|
||||
<field name="th" colspan="1" />
|
||||
<field name="fr" colspan="1" />
|
||||
<field name="sa" colspan="1" />
|
||||
<field name="su" colspan="1" />
|
||||
<newline />
|
||||
</group>
|
||||
<group col="10" colspan="4"
|
||||
attrs="{'invisible' : [('freq','!=','monthly'), ('freq','!=','yearly')]}">
|
||||
<group col="2" colspan="1">
|
||||
<field name="select1" />
|
||||
</group>
|
||||
<group col="2" colspan="1"
|
||||
attrs="{'invisible' : [('select1','=','day')]}">
|
||||
<field name="day"
|
||||
attrs="{'required' : [('select1','=','date')]}" />
|
||||
</group>
|
||||
<group col="3" colspan="1"
|
||||
attrs="{'invisible' : [('select1','=','date')]}">
|
||||
<field name="byday" string="The"
|
||||
attrs="{'required' : [('select1','=','day')]}" />
|
||||
<field name="week_list" nolabel="1"
|
||||
attrs="{'required' : [('select1','=','day')]}" />
|
||||
</group>
|
||||
<group col="1" colspan="1"
|
||||
attrs="{'invisible' : [('freq','!=','yearly')]}">
|
||||
<field name="month_list" string="of"
|
||||
colspan="1"
|
||||
attrs="{'required' : [('freq','=','yearly')]}" />
|
||||
</group>
|
||||
<field name="recurrency"/>
|
||||
<field name="edit_all" attrs="{'invisible':[('recurrency','=', False)]}"
|
||||
on_change="onchange_edit_all(rrule_type,edit_all)"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
|
@ -397,6 +344,57 @@
|
|||
</form>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Recurrency Option" attrs="{'invisible':[('recurrency','=',False)]}">
|
||||
<group col="4" colspan="4" name="rrule">
|
||||
<group col="4" colspan="4">
|
||||
<field name="rrule_type" string="Recurrency period"
|
||||
attrs="{'readonly':[('recurrent_uid','!=',False)]}" />
|
||||
<label string="" colspan="2"/>
|
||||
|
||||
<separator string="End of recurrency" colspan="4"/>
|
||||
<field name="count" attrs="{'readonly': [('end_date','!=',False)]}"/>
|
||||
<label string=" " colspan="2" />
|
||||
<newline />
|
||||
<field name="end_date" attrs="{'readonly': [('count','!=',False)]}"/>
|
||||
<newline />
|
||||
|
||||
<separator string="Repeat interval" colspan="4"/>
|
||||
<field name="interval" /> <label string="" colspan="2"/>
|
||||
</group>
|
||||
<group col="8" colspan="4" name="Select weekdays" attrs="{'invisible' :[('rrule_type','not in', ['weekly'])]}">
|
||||
<separator string="Choose day where repeat the meeting" colspan="8"/>
|
||||
<field name="mo" colspan="1" />
|
||||
<field name="tu" colspan="1" />
|
||||
<field name="we" colspan="1" />
|
||||
<field name="th" colspan="1" />
|
||||
<newline/>
|
||||
<field name="fr" colspan="1" />
|
||||
<field name="sa" colspan="1" />
|
||||
<field name="su" colspan="1" />
|
||||
<newline />
|
||||
</group>
|
||||
<group col="10" colspan="4"
|
||||
attrs="{'invisible' : [('rrule_type','!=','monthly')]}">
|
||||
<separator string="Choose day in the month where repeat the meeting" colspan="12"/>
|
||||
<group col="2" colspan="1">
|
||||
<field name="select1" />
|
||||
</group>
|
||||
<group col="2" colspan="1"
|
||||
attrs="{'invisible' : [('select1','=','day')]}">
|
||||
<field name="day"
|
||||
attrs="{'required' : [('select1','=','date'), ('rrule_type','=','monthly')]}" />
|
||||
</group>
|
||||
<group col="3" colspan="1"
|
||||
attrs="{'invisible' : [('select1','=','date'), ('rrule_type','=','monthly')]}">
|
||||
<field name="byday" string="The"
|
||||
attrs="{'required' : [('select1','=','day'), ('rrule_type','=','monthly')]}" />
|
||||
<field name="week_list" nolabel="1"
|
||||
attrs="{'required' : [('select1','=','day'), ('rrule_type','=','monthly')]}" />
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
Now I will set recurrence for this event to occur monday and friday of week
|
||||
-
|
||||
!python {model: calendar.event}: |
|
||||
self.write(cr, uid, [ref("calendar_event_technicalpresentation0")], {'fr': 1, 'mo': 1, 'interval': 1, 'freq': 'weekly', 'rrule_type': 'custom'})
|
||||
self.write(cr, uid, [ref("calendar_event_technicalpresentation0")], {'fr': 1, 'mo': 1, 'interval': 1, 'rrule_type': 'weekly'})
|
||||
- |
|
||||
In order to check that recurrent events are views successfully in calendar view,
|
||||
I will open calendar view of events
|
||||
|
|
|
@ -38,8 +38,6 @@ class base_contact_installer(osv.osv_memory):
|
|||
obj = self.pool.get("base.contact.installer").browse(cr, uid, uid, context=context)
|
||||
if obj.migrate:
|
||||
cr.execute("""DROP TRIGGER IF EXISTS contactjob on res_partner_contact;
|
||||
DROP LANGUAGE IF EXISTS plpgsql CASCADE;
|
||||
CREATE LANGUAGE plpgsql ;
|
||||
CREATE OR REPLACE FUNCTION add_to_job() RETURNS TRIGGER AS $contactjob$
|
||||
DECLARE
|
||||
new_name varchar;
|
||||
|
@ -57,7 +55,7 @@ class base_contact_installer(osv.osv_memory):
|
|||
cr.execute("INSERT into res_partner_contact (name, title, email, first_name, website) (SELECT coalesce(name, 'Noname'), title, email, function , to_char(id, '99999999') from res_partner_address)")
|
||||
|
||||
cr.execute("DROP TRIGGER IF EXISTS contactjob on res_partner_contact")
|
||||
cr.execute("DROP LANGUAGE IF EXISTS plpgsql CASCADE;")
|
||||
|
||||
cr.execute("DROP FUNCTION IF EXISTS add_to_job()")
|
||||
|
||||
base_contact_installer()
|
||||
|
|
|
@ -25,6 +25,7 @@ from StringIO import StringIO
|
|||
import base64
|
||||
import pooler
|
||||
import addons
|
||||
import sys
|
||||
|
||||
class report_xml(osv.osv):
|
||||
_inherit = 'ir.actions.report.xml'
|
||||
|
@ -59,6 +60,8 @@ class report_xml(osv.osv):
|
|||
|
||||
def report_get(self, cr, uid, report_id, context=None):
|
||||
report = self.browse(cr, uid, report_id, context=context)
|
||||
reload(sys)
|
||||
sys.setdefaultencoding( "latin-1" )
|
||||
return {
|
||||
'file_type' : report.report_type,
|
||||
'report_sxw_content': report.report_sxw_content and base64.encodestring(report.report_sxw_content) or False,
|
||||
|
|
Binary file not shown.
|
@ -579,8 +579,8 @@ class Calendar(CalDAV, osv.osv):
|
|||
string="Type", size=64),
|
||||
'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'),
|
||||
'create_date': fields.datetime('Created Date', readonly=True),
|
||||
'write_date': fields.datetime('Modifided Date', readonly=True),
|
||||
'description': fields.text("description"),
|
||||
'write_date': fields.datetime('Write Date', readonly=True),
|
||||
'description': fields.text("Description"),
|
||||
'calendar_color': fields.char('Color', size=20, help="For supporting clients, the color of the calendar entries"),
|
||||
'calendar_order': fields.integer('Order', help="For supporting clients, the order of this folder among the calendars"),
|
||||
'has_webcal': fields.boolean('WebCal', required=True, help="Also export a <name>.ics entry next to the calendar folder, with WebCal content."),
|
||||
|
@ -818,6 +818,7 @@ class basic_calendar_fields(osv.osv):
|
|||
|
||||
_name = 'basic.calendar.fields'
|
||||
_description = 'Calendar fields'
|
||||
_order = 'name'
|
||||
|
||||
_columns = {
|
||||
'field_id': fields.many2one('ir.model.fields', 'OpenObject Field'),
|
||||
|
@ -833,7 +834,7 @@ class basic_calendar_fields(osv.osv):
|
|||
}
|
||||
|
||||
_defaults = {
|
||||
'fn': lambda *a: 'field',
|
||||
'fn': 'field',
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
|
@ -1164,11 +1165,16 @@ class Alarm(CalDAV, osv.osv_memory):
|
|||
self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
|
||||
for child in ical_data.getChildren():
|
||||
if child.name.lower() == 'trigger':
|
||||
seconds = child.value.seconds
|
||||
days = child.value.days
|
||||
diff = (days * 86400) + seconds
|
||||
interval = 'days'
|
||||
related = 'before'
|
||||
if isinstance(child.value, timedelta):
|
||||
seconds = child.value.seconds
|
||||
days = child.value.days
|
||||
diff = (days * 86400) + seconds
|
||||
interval = 'days'
|
||||
related = 'before'
|
||||
elif isinstance(child.value, datetime):
|
||||
# TODO
|
||||
# remember, spec says this datetime is in UTC
|
||||
raise NotImplementedError("we cannot parse absolute triggers")
|
||||
if not seconds:
|
||||
duration = abs(days)
|
||||
related = days > 0 and 'after' or 'before'
|
||||
|
|
|
@ -82,8 +82,11 @@ class calendar_event_import(osv.osv_memory):
|
|||
'msg': fields.text('', readonly=True),
|
||||
}
|
||||
|
||||
def _get_msg(self, cr, uid, context):
|
||||
return _('Import Sucessful')
|
||||
|
||||
_defaults = {
|
||||
'msg':lambda *a:'Import Sucessful'
|
||||
'msg': _get_msg,
|
||||
}
|
||||
|
||||
calendar_event_import()
|
||||
|
|
|
@ -153,7 +153,7 @@
|
|||
<field name="usage">menu</field>
|
||||
<field name="view_id" ref="board_crm_form"/>
|
||||
</record>
|
||||
<menuitem id="board.menu_dasboard" name="Dashboard" sequence="0" parent="base.next_id_64"/>
|
||||
<menuitem id="board.menu_dasboard" name="Dashboard" sequence="0" parent="base.next_id_64"/>
|
||||
<menuitem
|
||||
name="Sales Dashboard" parent="board.menu_dasboard"
|
||||
action="open_board_crm"
|
||||
|
|
|
@ -47,7 +47,68 @@ AVAILABLE_PRIORITIES = [
|
|||
class crm_case(object):
|
||||
"""A simple python class to be used for common functions """
|
||||
|
||||
|
||||
def _find_lost_stage(self, cr, uid, type, section_id):
|
||||
return self._find_percent_stage(cr, uid, 0.0, type, section_id)
|
||||
|
||||
def _find_won_stage(self, cr, uid, type, section_id):
|
||||
return self._find_percent_stage(cr, uid, 100.0, type, section_id)
|
||||
|
||||
def _find_percent_stage(self, cr, uid, percent, type, section_id):
|
||||
"""
|
||||
Return the first stage with a probability == percent
|
||||
"""
|
||||
stage_pool = self.pool.get('crm.case.stage')
|
||||
if section_id :
|
||||
ids = stage_pool.search(cr, uid, [("probability", '=', percent), ("type", 'like', type), ("section_ids", 'in', [section_id])])
|
||||
else :
|
||||
ids = stage_pool.search(cr, uid, [("probability", '=', percent), ("type", 'like', type)])
|
||||
|
||||
if ids:
|
||||
return ids[0]
|
||||
return False
|
||||
|
||||
|
||||
def _find_first_stage(self, cr, uid, type, section_id):
|
||||
"""
|
||||
return the first stage that has a sequence number equal or higher than sequence
|
||||
"""
|
||||
stage_pool = self.pool.get('crm.case.stage')
|
||||
if section_id :
|
||||
ids = stage_pool.search(cr, uid, [("sequence", '>', 0), ("type", 'like', type), ("section_ids", 'in', [section_id])])
|
||||
else :
|
||||
ids = stage_pool.search(cr, uid, [("sequence", '>', 0), ("type", 'like', type)])
|
||||
|
||||
if ids:
|
||||
stages = stage_pool.browse(cr, uid, ids)
|
||||
stage_min = stages[0]
|
||||
for stage in stages:
|
||||
if stage_min.sequence > stage.sequence:
|
||||
stage_min = stage
|
||||
return stage_min.id
|
||||
else :
|
||||
return False
|
||||
|
||||
def onchange_stage_id(self, cr, uid, ids, stage_id, context={}):
|
||||
|
||||
""" @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 stage’s IDs
|
||||
@stage_id: change state id on run time """
|
||||
|
||||
if not stage_id:
|
||||
return {'value':{}}
|
||||
|
||||
stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
|
||||
|
||||
if not stage.on_change:
|
||||
return {'value':{}}
|
||||
return {'value':{'probability': stage.probability}}
|
||||
|
||||
|
||||
def _get_default_partner_address(self, cr, uid, context=None):
|
||||
|
||||
"""Gives id of default address for current user
|
||||
@param self: The object pointer
|
||||
@param cr: the current row, from the database cursor,
|
||||
|
@ -149,6 +210,9 @@ class crm_case(object):
|
|||
return False
|
||||
|
||||
next_seq = next_stage.sequence
|
||||
if not current_seq :
|
||||
current_seq = 0
|
||||
|
||||
if (abs(next_seq - current_seq)) >= 1:
|
||||
return next_stage
|
||||
else :
|
||||
|
@ -157,17 +221,21 @@ class crm_case(object):
|
|||
def stage_change(self, cr, uid, ids, context=None, order='sequence'):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
stage_pool = self.pool.get('crm.case.stage')
|
||||
stage_type = context and context.get('stage_type','')
|
||||
|
||||
current_seq = False
|
||||
next_stage_id = False
|
||||
|
||||
for case in self.browse(cr, uid, ids, context=context):
|
||||
next_stage = False
|
||||
data = {}
|
||||
value = {}
|
||||
if case.section_id.id :
|
||||
domain = [('type', '=', stage_type),('section_ids', '=', case.section_id.id)]
|
||||
else :
|
||||
domain = [('type', '=', stage_type)]
|
||||
|
||||
domain = [('type', '=', stage_type),('section_ids', '=', case.section_id.id)]
|
||||
if case.section_id and case.section_id.stage_ids:
|
||||
domain.append(('id', 'in', map(lambda x: x.id, case.section_id.stage_ids)))
|
||||
|
||||
stages = stage_pool.search(cr, uid, domain, order=order)
|
||||
current_seq = case.stage_id.sequence
|
||||
|
@ -176,14 +244,16 @@ class crm_case(object):
|
|||
index = stages.index(case.stage_id.id)
|
||||
|
||||
next_stage = self._find_next_stage(cr, uid, stages, index, current_seq, stage_pool, context=context)
|
||||
|
||||
if next_stage:
|
||||
next_stage_id = next_stage.id
|
||||
data = {'stage_id': next_stage.id}
|
||||
value.update({'stage_id': next_stage.id})
|
||||
if next_stage.on_change:
|
||||
data.update({'probability': next_stage.probability})
|
||||
self.write(cr, uid, [case.id], data, context=context)
|
||||
value.update({'probability': next_stage.probability})
|
||||
self.write(cr, uid, [case.id], value, context=context)
|
||||
|
||||
return next_stage_id
|
||||
|
||||
return next_stage_id #FIXME should return a list of all id
|
||||
|
||||
|
||||
def stage_next(self, cr, uid, ids, context=None):
|
||||
|
@ -194,7 +264,6 @@ class crm_case(object):
|
|||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of case IDs
|
||||
@param context: A standard dictionary for contextual values"""
|
||||
|
||||
|
||||
return self.stage_change(cr, uid, ids, context=context, order='sequence')
|
||||
|
||||
|
@ -257,6 +326,7 @@ class crm_case(object):
|
|||
@param ids: List of case Ids
|
||||
@param *args: Tuple Value for additional Params
|
||||
"""
|
||||
|
||||
cases = self.browse(cr, uid, ids)
|
||||
self._history(cr, uid, cases, _('Open'))
|
||||
for case in cases:
|
||||
|
@ -264,7 +334,9 @@ class crm_case(object):
|
|||
if not case.user_id:
|
||||
data['user_id'] = uid
|
||||
self.write(cr, uid, case.id, data)
|
||||
self._action(cr, uid, cases, 'open')
|
||||
|
||||
|
||||
self._action(cr, uid, cases, 'open')
|
||||
return True
|
||||
|
||||
def case_close(self, cr, uid, ids, *args):
|
||||
|
@ -483,6 +555,12 @@ class crm_case_stage(osv.osv):
|
|||
_description = "Stage of case"
|
||||
_rec_name = 'name'
|
||||
_order = "sequence"
|
||||
|
||||
|
||||
|
||||
def _get_type_value(self, cr, user, context):
|
||||
return [('lead','Lead'),('opportunity','Opportunity')]
|
||||
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Stage Name', size=64, required=True, translate=True),
|
||||
|
@ -491,9 +569,10 @@ class crm_case_stage(osv.osv):
|
|||
'on_change': fields.boolean('Change Probability Automatically', \
|
||||
help="Change Probability on next and previous stages."),
|
||||
'requirements': fields.text('Requirements'),
|
||||
'type': fields.selection([('lead','Lead'),('opportunity','Opportunity'),('claim','Claim'), ('fundraising','Fundraising')], 'Type'),
|
||||
'type': fields.selection(_get_type_value, 'Type'),
|
||||
}
|
||||
|
||||
|
||||
|
||||
def _find_stage_type(self, cr, uid, context=None):
|
||||
"""Finds type of stage according to object.
|
||||
@param self: The object pointer
|
||||
|
|
|
@ -151,18 +151,6 @@ class crm_lead(crm_case, osv.osv):
|
|||
'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
|
||||
}
|
||||
|
||||
def _get_stage_id(self, cr, uid, context=None):
|
||||
"""Finds type of stage according to object.
|
||||
@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 context: A standard dictionary for contextual values
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
type = context and context.get('stage_type', '')
|
||||
stage_ids = self.pool.get('crm.case.stage').search(cr, uid, [('type','=',type),('sequence','>=',1)])
|
||||
return stage_ids and stage_ids[0] or False
|
||||
|
||||
_defaults = {
|
||||
'active': lambda *a: 1,
|
||||
|
@ -173,8 +161,10 @@ class crm_lead(crm_case, osv.osv):
|
|||
'section_id': crm_case._get_section,
|
||||
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
|
||||
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
|
||||
'stage_id': _get_stage_id,
|
||||
#'stage_id': _get_stage_id,
|
||||
}
|
||||
|
||||
|
||||
|
||||
def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
|
||||
"""This function returns value of partner email based on Partner Address
|
||||
|
@ -198,27 +188,30 @@ class crm_lead(crm_case, osv.osv):
|
|||
@param ids: List of case's Ids
|
||||
@param *args: Give Tuple Value
|
||||
"""
|
||||
old_state = self.read(cr, uid, ids, ['state'])[0]['state']
|
||||
old_stage_id = self.read(cr, uid, ids, ['stage_id'])[0]['stage_id']
|
||||
leads = self.browse(cr, uid, ids)
|
||||
|
||||
|
||||
|
||||
for i in xrange(0, len(ids)):
|
||||
if leads[i].state == 'draft':
|
||||
value = {}
|
||||
if not leads[i].stage_id :
|
||||
stage_id = self._find_first_stage(cr, uid, leads[i].type, leads[i].section_id.id or False)
|
||||
value.update({'stage_id' : stage_id})
|
||||
value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
|
||||
self.write(cr, uid, [ids[i]], value)
|
||||
self.log_open( cr, uid, leads[i])
|
||||
res = super(crm_lead, self).case_open(cr, uid, ids, *args)
|
||||
if old_state == 'draft':
|
||||
value = {}
|
||||
if not old_stage_id:
|
||||
stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
|
||||
if stage_id:
|
||||
value.update(self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value'])
|
||||
value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
|
||||
self.write(cr, uid, ids, value)
|
||||
|
||||
for case in self.browse(cr, uid, ids):
|
||||
if case.type == 'lead':
|
||||
message = _("The lead '%s' has been opened.") % case.name
|
||||
elif case.type == 'opportunity':
|
||||
message = _("The opportunity '%s' has been opened.") % case.name
|
||||
else:
|
||||
message = _("The case '%s' has been opened.") % case.name
|
||||
self.log(cr, uid, case.id, message)
|
||||
return res
|
||||
|
||||
def log_open(self, cr, uid, case):
|
||||
if case.type == 'lead':
|
||||
message = _("The lead '%s' has been opened.") % case.name
|
||||
elif case.type == 'opportunity':
|
||||
message = _("The opportunity '%s' has been opened.") % case.name
|
||||
else:
|
||||
message = _("The case '%s' has been opened.") % case.name
|
||||
self.log(cr, uid, case.id, message)
|
||||
|
||||
def case_close(self, cr, uid, ids, *args):
|
||||
"""Overrides close for crm_case for setting close date
|
||||
|
@ -228,7 +221,7 @@ class crm_lead(crm_case, osv.osv):
|
|||
@param ids: List of case Ids
|
||||
@param *args: Tuple Value for additional Params
|
||||
"""
|
||||
res = super(crm_lead, self).case_close(cr, uid, ids, args)
|
||||
res = super(crm_lead, self).case_close(cr, uid, ids, *args)
|
||||
self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
|
||||
for case in self.browse(cr, uid, ids):
|
||||
if case.type == 'lead':
|
||||
|
@ -296,14 +289,18 @@ class crm_lead(crm_case, osv.osv):
|
|||
return value
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
if 'date_closed' in vals:
|
||||
return super(crm_lead,self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
if 'stage_id' in vals and vals['stage_id']:
|
||||
stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context)
|
||||
self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
|
||||
message=''
|
||||
for case in self.browse(cr, uid, ids, context=context):
|
||||
if case.type == 'lead':
|
||||
if case.type == 'lead' or context.get('stage_type',False)=='lead':
|
||||
message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
|
||||
elif case.type == 'opportunity':
|
||||
message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
|
||||
|
@ -398,7 +395,6 @@ class crm_lead(crm_case, osv.osv):
|
|||
if case.state in CRM_LEAD_PENDING_STATES:
|
||||
values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
|
||||
res = self.write(cr, uid, [case.id], values, context=context)
|
||||
|
||||
return res
|
||||
|
||||
def msg_send(self, cr, uid, id, *args, **argv):
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
<field name="context">{'type':'lead'}</field>
|
||||
<field name="help">Create specific stages that will help your sales better organise their sales pipeline by maintaining them to their leads and sales opportunities. It will allow them to easily track how is positioned a specific lead or opportunity in the sales cycle.</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
<menuitem action="crm_lead_stage_act" id="menu_crm_lead_stage_act" name="Stages"
|
||||
groups="base.group_extended" sequence="0"
|
||||
|
|
|
@ -72,53 +72,14 @@ class crm_meeting(crm_case, osv.osv):
|
|||
('draft', 'Unconfirmed'),
|
||||
('cancel', 'Cancelled'),
|
||||
('done', 'Done')], 'State', \
|
||||
size=16, readonly=True)
|
||||
size=16, readonly=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'state': lambda *a: 'draft',
|
||||
'active': lambda *a: 1,
|
||||
'user_id': lambda self, cr, uid, ctx: uid,
|
||||
}
|
||||
|
||||
def open_meeting(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Open Crm Meeting Form for Crm Meeting.
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of crm meeting’s IDs
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: Dictionary value which open Crm Meeting form.
|
||||
"""
|
||||
|
||||
data_obj = self.pool.get('ir.model.data')
|
||||
|
||||
value = {}
|
||||
|
||||
id2 = data_obj._get_id(cr, uid, 'crm', 'crm_case_form_view_meet')
|
||||
id3 = data_obj._get_id(cr, uid, 'crm', 'crm_case_tree_view_meet')
|
||||
id4 = data_obj._get_id(cr, uid, 'crm', 'crm_case_calendar_view_meet')
|
||||
if id2:
|
||||
id2 = data_obj.browse(cr, uid, id2, context=context).res_id
|
||||
if id3:
|
||||
id3 = data_obj.browse(cr, uid, id3, context=context).res_id
|
||||
if id4:
|
||||
id4 = data_obj.browse(cr, uid, id4, context=context).res_id
|
||||
for id in ids:
|
||||
value = {
|
||||
'name': _('Meeting'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form,tree',
|
||||
'res_model': 'crm.meeting',
|
||||
'view_id': False,
|
||||
'views': [(id2, 'form'), (id3, 'tree'), (id4, 'calendar')],
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_id': base_calendar.base_calendar_id2real_id(id),
|
||||
'nodestroy': True
|
||||
}
|
||||
|
||||
return value
|
||||
|
||||
def case_open(self, cr, uid, ids, *args):
|
||||
"""Confirms meeting
|
||||
@param self: The object pointer
|
||||
|
|
|
@ -44,62 +44,9 @@
|
|||
<field name="alarm_id" string="Reminder"
|
||||
widget="selection" />
|
||||
<group colspan="2" col="4" >
|
||||
<field name="rrule_type" string="Recurrency"
|
||||
colspan="1" attrs="{'readonly':[('recurrent_uid','!=',False)]}"/>
|
||||
<button string="Edit All"
|
||||
help="Edit all Occurrences of recurrent Meeting"
|
||||
attrs="{'invisible':[('rrule_type','in', ('none', False))]}"
|
||||
name="open_meeting" icon="gtk-edit"
|
||||
type="object" />
|
||||
<button string="Exclude range" groups="base.group_extended"
|
||||
help="Add Exception Rule"
|
||||
name="%(base_calendar.action_base_calendar_set_exrule)d" icon="gtk-zoom-out" type="action"
|
||||
context="{'model' : 'crm.meeting'}"
|
||||
attrs="{'invisible':[('rrule_type','in', ('none', False))]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group col="4" colspan="4" name="rrule" attrs="{'invisible': [('rrule_type','!=','custom')]}">
|
||||
<separator string="Custom Recurrency Rule" colspan="8"/>
|
||||
<group col="8" colspan="4">
|
||||
<field name="interval" />
|
||||
<field name="freq" />
|
||||
<field name="count" />
|
||||
<field name="end_date" />
|
||||
</group>
|
||||
<group col="14" colspan="4" name="Select weekdays"
|
||||
attrs="{'invisible' : [('freq','!=','weekly')]}">
|
||||
<field name="mo" colspan="1" />
|
||||
<field name="tu" colspan="1" />
|
||||
<field name="we" colspan="1" />
|
||||
<field name="th" colspan="1" />
|
||||
<field name="fr" colspan="1" />
|
||||
<field name="sa" colspan="1" />
|
||||
<field name="su" colspan="1" />
|
||||
<newline />
|
||||
</group>
|
||||
<group col="10" colspan="4"
|
||||
attrs="{'invisible' : [('freq','!=','monthly'), ('freq','!=','yearly')]}">
|
||||
<group col="2" colspan="1">
|
||||
<field name="select1" />
|
||||
</group>
|
||||
<group col="2" colspan="1"
|
||||
attrs="{'invisible' : [('select1','=','day')]}">
|
||||
<field name="day"
|
||||
attrs="{'required' : [('select1','=','date')]}" />
|
||||
</group>
|
||||
<group col="3" colspan="1"
|
||||
attrs="{'invisible' : [('select1','=','date')]}">
|
||||
<field name="byday" string="The"
|
||||
attrs="{'required' : [('select1','=','day')]}" />
|
||||
<field name="week_list" nolabel="1"
|
||||
attrs="{'required' : [('select1','=','day')]}" />
|
||||
</group>
|
||||
<group col="1" colspan="1"
|
||||
attrs="{'invisible' : [('freq','!=','yearly')]}">
|
||||
<field name="month_list" string="of"
|
||||
colspan="1"
|
||||
attrs="{'required' : [('freq','=','yearly')]}" />
|
||||
</group>
|
||||
<field name="recurrency"/>
|
||||
<field name="edit_all" attrs="{'invisible':[('recurrency','=', False)]}"
|
||||
on_change="onchange_edit_all(rrule_type,edit_all)"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
|
@ -133,14 +80,11 @@
|
|||
<separator colspan="4"/>
|
||||
<group col="8" colspan="4" groups="base.group_extended">
|
||||
<field name="state" />
|
||||
<button name="case_cancel" string="Cancel"
|
||||
states="draft,open" type="object"
|
||||
icon="gtk-cancel" />
|
||||
<button name="case_close" string="Done"
|
||||
states="open" type="object"
|
||||
icon="gtk-jump-to" />
|
||||
<button name="case_reset" string="Reset to Unconfirmed"
|
||||
states="open,done,cancel" type="object"
|
||||
states="open,done" type="object"
|
||||
icon="gtk-convert" />
|
||||
<button name="case_open" string="Confirm"
|
||||
states="draft" type="object"
|
||||
|
@ -221,6 +165,57 @@
|
|||
</form>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Recurrency Option" attrs="{'invisible':[('recurrency','=',False)]}">
|
||||
<group col="4" colspan="4" name="rrule">
|
||||
<group col="4" colspan="4">
|
||||
<field name="rrule_type" string="Recurrency period"
|
||||
attrs="{'readonly':[('recurrent_uid','!=',False)]}" />
|
||||
<label string="" colspan="2"/>
|
||||
|
||||
<separator string="End of recurrency" colspan="4"/>
|
||||
<field name="count" attrs="{'readonly': [('end_date','!=',False)]}"/>
|
||||
<label string=" " colspan="2" />
|
||||
<newline />
|
||||
<field name="end_date" attrs="{'readonly': [('count','!=',False)]}"/>
|
||||
<newline />
|
||||
|
||||
<separator string="Repeat interval" colspan="4"/>
|
||||
<field name="interval" /> <label string="" colspan="2"/>
|
||||
</group>
|
||||
<group col="8" colspan="4" name="Select weekdays" attrs="{'invisible' :[('rrule_type','not in', ['weekly'])]}">
|
||||
<separator string="Choose day where repeat the meeting" colspan="8"/>
|
||||
<field name="mo" colspan="1" />
|
||||
<field name="tu" colspan="1" />
|
||||
<field name="we" colspan="1" />
|
||||
<field name="th" colspan="1" />
|
||||
<newline/>
|
||||
<field name="fr" colspan="1" />
|
||||
<field name="sa" colspan="1" />
|
||||
<field name="su" colspan="1" />
|
||||
<newline />
|
||||
</group>
|
||||
<group col="10" colspan="4"
|
||||
attrs="{'invisible' : [('rrule_type','!=','monthly')]}">
|
||||
<separator string="Choose day in the month where repeat the meeting" colspan="12"/>
|
||||
<group col="2" colspan="1">
|
||||
<field name="select1" />
|
||||
</group>
|
||||
<group col="2" colspan="1"
|
||||
attrs="{'invisible' : [('select1','=','day')]}">
|
||||
<field name="day"
|
||||
attrs="{'required' : [('select1','=','date'), ('rrule_type','=','monthly')]}" />
|
||||
</group>
|
||||
<group col="3" colspan="1"
|
||||
attrs="{'invisible' : [('select1','=','date'), ('rrule_type','=','monthly')]}">
|
||||
<field name="byday" string="The"
|
||||
attrs="{'required' : [('select1','=','day'), ('rrule_type','=','monthly')]}" />
|
||||
<field name="week_list" nolabel="1"
|
||||
attrs="{'required' : [('select1','=','day'), ('rrule_type','=','monthly')]}" />
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -245,11 +240,8 @@
|
|||
<field name="state" groups="base.group_extended"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="show_as" invisible="1" string="Show time as"/>
|
||||
<button name="case_cancel" string="Cancel"
|
||||
states="draft,open" type="object"
|
||||
icon="gtk-cancel" />
|
||||
<button name="case_reset" string="Reset to Unconfirmed"
|
||||
states="open,done,cancel" type="object"
|
||||
states="open,done" type="object"
|
||||
icon="gtk-convert" />
|
||||
<button name="case_open" string="Confirm"
|
||||
states="draft" type="object"
|
||||
|
@ -304,7 +296,7 @@
|
|||
<group col="12" colspan="4">
|
||||
<filter icon="terp-check" name="current" string="Current"
|
||||
domain="[('state','in',('draft', 'open'))]"
|
||||
groups="base.group_extended"
|
||||
groups="base.group_extended"
|
||||
help="Current Meetings"/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="name" select="1" string="Subject"/>
|
||||
|
@ -333,8 +325,8 @@
|
|||
context="{'group_by':'partner_id'}" />
|
||||
<separator orientation="vertical" />
|
||||
<filter string="Type" help="Meeting Type"
|
||||
icon="terp-stock_symbol-selection" domain="[]"
|
||||
context="{'group_by':'categ_id'}" />
|
||||
icon="terp-stock_symbol-selection" domain="[]"
|
||||
context="{'group_by':'categ_id'}" />
|
||||
<filter string="Privacy" icon="terp-locked"
|
||||
domain="[]" context="{'group_by':'class'}" />
|
||||
<filter string="Show time as" icon="terp-project"
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
from osv import fields, osv
|
||||
from tools.translate import _
|
||||
import crm
|
||||
import time
|
||||
|
||||
|
||||
AVAILABLE_STATES = [
|
||||
('draft','Draft'),
|
||||
|
@ -53,6 +53,28 @@ class crm_opportunity(osv.osv):
|
|||
'stage_id': fields.many2one('crm.case.stage', 'Stage', domain="[('type','=','opportunity')]"),
|
||||
}
|
||||
|
||||
def _case_close_generic(self, cr, uid, ids, find_stage, *args):
|
||||
res = super(crm_opportunity, self).case_close(cr, uid, ids, *args)
|
||||
for case in self.browse(cr, uid, ids):
|
||||
#if the case is not an opportunity close won't change the stage
|
||||
if not case.type == 'opportunity':
|
||||
return res
|
||||
|
||||
value = {}
|
||||
stage_id = find_stage(cr, uid, 'opportunity', case.section_id.id or False)
|
||||
if stage_id:
|
||||
stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage_id)
|
||||
value.update({'stage_id': stage_id})
|
||||
if stage_obj.on_change:
|
||||
value.update({'probability': stage_obj.probability})
|
||||
|
||||
#Done un crm.case
|
||||
#value.update({'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
|
||||
|
||||
|
||||
self.write(cr, uid, ids, value)
|
||||
return res
|
||||
|
||||
def case_close(self, cr, uid, ids, *args):
|
||||
"""Overrides close for crm_case for setting probability and close date
|
||||
@param self: The object pointer
|
||||
|
@ -61,16 +83,8 @@ class crm_opportunity(osv.osv):
|
|||
@param ids: List of case Ids
|
||||
@param *args: Tuple Value for additional Params
|
||||
"""
|
||||
res = super(crm_opportunity, self).case_close(cr, uid, ids, args)
|
||||
data_obj = self.pool.get('ir.model.data')
|
||||
data_id = data_obj._get_id(cr, uid, 'crm', 'stage_lead5')
|
||||
stage_id = data_obj.browse(cr, uid, data_id).res_id
|
||||
stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage_id)
|
||||
value = {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S'), 'stage_id': stage_id}
|
||||
if stage_obj.on_change:
|
||||
value.update({'probability': stage_obj.probability})
|
||||
|
||||
self.write(cr, uid, ids, value)
|
||||
res = self._case_close_generic(cr, uid, ids, self._find_won_stage, *args)
|
||||
|
||||
for (id, name) in self.name_get(cr, uid, ids):
|
||||
opp = self.browse(cr, uid, id)
|
||||
if opp.type == 'opportunity':
|
||||
|
@ -86,16 +100,8 @@ class crm_opportunity(osv.osv):
|
|||
@param ids: List of case Ids
|
||||
@param *args: Tuple Value for additional Params
|
||||
"""
|
||||
res = super(crm_opportunity, self).case_close(cr, uid, ids, args)
|
||||
data_obj = self.pool.get('ir.model.data')
|
||||
data_id = data_obj._get_id(cr, uid, 'crm', 'stage_lead6')
|
||||
stage_id = data_obj.browse(cr, uid, data_id).res_id
|
||||
stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage_id)
|
||||
value = {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S'), 'stage_id': stage_id}
|
||||
if stage_obj.on_change:
|
||||
value.update({'probability': stage_obj.probability})
|
||||
|
||||
self.write(cr, uid, ids, value)
|
||||
res = self._case_close_generic(cr, uid, ids, self._find_lost_stage, *args)
|
||||
|
||||
for (id, name) in self.name_get(cr, uid, ids):
|
||||
opp = self.browse(cr, uid, id)
|
||||
if opp.type == 'opportunity':
|
||||
|
@ -137,7 +143,7 @@ class crm_opportunity(osv.osv):
|
|||
@param *args: Give Tuple Value
|
||||
"""
|
||||
res = super(crm_opportunity, self).case_open(cr, uid, ids, *args)
|
||||
self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
|
||||
|
||||
return res
|
||||
|
||||
def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
<field name="context">{'type':'opportunity'}</field>
|
||||
<field name="help">Create specific stages that will help your sales better organise their sales pipeline by maintaining them to their sales opportunities. It will allow them to easily track how is positioned a specific opportunity in the sales cycle.</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
<menuitem action="crm_opportunity_stage_act" id="menu_crm_opportunity_stage_act" name="Stages"
|
||||
groups="base.group_extended" sequence="0"
|
||||
|
@ -39,14 +41,15 @@
|
|||
</group>
|
||||
<field name="user_id"/>
|
||||
|
||||
<button name="action_makeMeeting" type="object"
|
||||
string="Schedule Meeting" icon="gtk-redo" />
|
||||
<button string="Schedule/Log Call"
|
||||
name="%(opportunity2phonecall_act)d" icon="terp-call-start" type="action" groups="base.group_extended"/>
|
||||
|
||||
<field name="planned_revenue"/>
|
||||
<field name="probability"/>
|
||||
<field name="date_deadline"/>
|
||||
<button string="Schedule/Log Call"
|
||||
name="%(opportunity2phonecall_act)d" icon="terp-call-start" type="action" groups="base.group_extended"/>
|
||||
|
||||
<button name="action_makeMeeting" type="object"
|
||||
string="Schedule Meeting" icon="gtk-redo" />
|
||||
<newline/>
|
||||
<field name="date_action"/>
|
||||
<field name="title_action"/>
|
||||
|
|
|
@ -108,6 +108,7 @@
|
|||
<field name="sequence"/>
|
||||
<field name="name"/>
|
||||
<field name="probability"/>
|
||||
<field name="type" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -118,17 +119,19 @@
|
|||
<field name="name">crm.case.stage.form</field>
|
||||
<field name="model">crm.case.stage</field>
|
||||
<field name="type">form</field>
|
||||
<field name="priority" eval="1"/>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Stage">
|
||||
<separator string="Stage Definition" colspan="4"/>
|
||||
<field name="name" select="1"/>
|
||||
<field name="type" invisible="1" />
|
||||
<field name="section_ids" invisible="1" />
|
||||
|
||||
<field name="sequence"/>
|
||||
<field name="probability"/>
|
||||
<field name="on_change"/>
|
||||
<separator string="Requirements" colspan="4"/>
|
||||
<field name="requirements" nolabel="1" colspan="4"/>
|
||||
<field name="section_ids" invisible="1" />
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -141,6 +144,7 @@
|
|||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="crm_case_stage_tree"/>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Case Categories Form View -->
|
||||
|
||||
|
@ -375,7 +379,7 @@
|
|||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field eval="18" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="parent_id" position="after">
|
||||
<field name="user_id" position="after">
|
||||
<field name="section_id" completion="1" widget="selection"
|
||||
groups="base.group_extended"/>
|
||||
</field>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -64,6 +64,73 @@
|
|||
<button name="case_close" string="Held" states="open,draft,pending" type="object" icon="gtk-jump-to"/>
|
||||
<button name="case_pending" string="Not Held" states="open" type="object" icon="gtk-media-pause"/>
|
||||
</tree>
|
||||
<form string="Phone Call">
|
||||
<group colspan="6" col="7">
|
||||
<field name="name" required="1"/>
|
||||
<field name="partner_phone" attrs="{'required': [('categ_id.name', '=', 'Outbound')]}"/>
|
||||
<field name="duration" widget="float_time" required="1"/>
|
||||
<button string="Schedule a Meeting" name="action_make_meeting" icon="gtk-redo" type="object"/>
|
||||
|
||||
<field name="date" required="1"/>
|
||||
<field name="user_id"/>
|
||||
<field name="section_id" colspan="1" widget="selection" />
|
||||
<button string="aaaaaaaaaConvert to Opportunity"
|
||||
name="%(phonecall2opportunity_act)d"
|
||||
icon="gtk-index" type="action"
|
||||
attrs="{'invisible':[('opportunity_id','!=',False)]}" />
|
||||
<label colspan="6" string=""/>
|
||||
<button string="Schedule Other Call"
|
||||
icon="terp-call-start"
|
||||
name="%(phonecall_to_phonecall_act)d"
|
||||
type="action" />
|
||||
|
||||
</group>
|
||||
|
||||
<group col="3" colspan="2">
|
||||
<separator colspan="3" string="Contacts" />
|
||||
<field name="partner_id"
|
||||
on_change="onchange_partner_id(partner_id, email_from)" />
|
||||
<button string="Create a Partner"
|
||||
icon="terp-partner"
|
||||
name="%(action_crm_phonecall2partner)d"
|
||||
type="action"
|
||||
attrs="{'invisible':[('partner_id','!=',False)]}" />
|
||||
<newline/>
|
||||
<field name="partner_address_id"
|
||||
on_change="onchange_partner_address_id(partner_address_id, email_from)" />
|
||||
<newline/>
|
||||
<field name="partner_mobile" />
|
||||
</group>
|
||||
<group col="2" colspan="2">
|
||||
<separator colspan="2" string="Categorization" />
|
||||
<field name="categ_id" widget="selection"
|
||||
domain="[('object_id.model', '=', 'crm.phonecall')]"
|
||||
string="Type" />
|
||||
<field name="priority"/>
|
||||
<field name="opportunity_id"/>
|
||||
</group>
|
||||
<separator string="Description" colspan="4" />
|
||||
<field name="description" nolabel="1" colspan="4" />
|
||||
<separator colspan="4" />
|
||||
<group col="8" colspan="4">
|
||||
<field name="state" select="1" />
|
||||
<button name="case_cancel" string="Cancel"
|
||||
states="draft,open,pending" type="object"
|
||||
icon="gtk-cancel" />
|
||||
<button name="case_open" string="Open"
|
||||
states="draft,pending" type="object"
|
||||
icon="gtk-go-forward" />
|
||||
<button name="case_pending" string="Not Held"
|
||||
states="open" type="object" icon="gtk-media-pause" />
|
||||
<button name="case_close" string="Held"
|
||||
states="open,draft,pending" type="object"
|
||||
icon="gtk-jump-to" />
|
||||
<button name="case_reset" string="Reset to Draft"
|
||||
states="done,cancel" type="object"
|
||||
icon="gtk-convert" />
|
||||
</group>
|
||||
</form>
|
||||
|
||||
</field>
|
||||
</page>
|
||||
</field>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
and I set the fields so that the meeting will occur weekly on Monday and Friday 10 times
|
||||
-
|
||||
!python {model: crm.meeting}: |
|
||||
self.write(cr, uid, [ref("crm_meeting_regardingpresentation0")], {'fr': 1, 'mo': 1, 'th': 1, 'tu': 1, 'we':1, 'count':10, 'interval': 1, 'freq': 'weekly', 'rrule_type': 'custom'})
|
||||
self.write(cr, uid, [ref("crm_meeting_regardingpresentation0")], {'fr': 1, 'mo': 1, 'th': 1, 'tu': 1, 'we':1, 'count':10, 'interval': 1, 'rrule_type': 'weekly'})
|
||||
|
||||
- |
|
||||
I can see from the calendar view that the meeting is scheduled on Monday and Friday
|
||||
|
@ -62,7 +62,7 @@
|
|||
Now If I want to edit meetings information for all occurrence I click on "Edit All" button.
|
||||
-
|
||||
!python {model: crm.meeting}: |
|
||||
self.open_meeting(cr, uid, [ref('crm_meeting_regardingpresentation0')])
|
||||
self.write(cr, uid, [ref('crm_meeting_regardingpresentation0')], {'edit_all':'True'},context)
|
||||
- |
|
||||
I can see that new meeting form is opened with same value
|
||||
I change some data for meeting and save it
|
||||
|
|
|
@ -89,7 +89,7 @@ class crm_lead2opportunity(osv.osv_memory):
|
|||
self.pool.get('mailgate.message').write(cr, uid, msg_ids, {
|
||||
'partner_id': lead.partner_id.id
|
||||
}, context=context)
|
||||
self.log(cr, uid, lead.id,
|
||||
leads.log(cr, uid, lead.id,
|
||||
_("Lead '%s' has been converted to an opportunity.") % lead.name)
|
||||
|
||||
return {
|
||||
|
|
|
@ -96,7 +96,7 @@ class crm_claim(crm.crm_case, osv.osv):
|
|||
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
|
||||
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
|
||||
'stage_id': _get_stage_id,
|
||||
#'stage_id': _get_stage_id,
|
||||
}
|
||||
|
||||
def onchange_partner_id(self, cr, uid, ids, part, email=False):
|
||||
|
@ -132,7 +132,38 @@ class crm_claim(crm.crm_case, osv.osv):
|
|||
return {'value': {'email_from': False}}
|
||||
address = self.pool.get('res.partner.address').browse(cr, uid, add)
|
||||
return {'value': {'email_from': address.email, 'partner_phone': address.phone, 'partner_mobile': address.mobile}}
|
||||
|
||||
def case_open(self, cr, uid, ids, *args):
|
||||
"""
|
||||
Opens Claim
|
||||
"""
|
||||
res = super(crm_claim, self).case_open(cr, uid, ids, *args)
|
||||
claims = self.browse(cr, uid, ids)
|
||||
|
||||
for i in xrange(0, len(ids)):
|
||||
if not claims[i].stage_id :
|
||||
stage_id = self._find_first_stage(cr, uid, 'claim', claims[i].section_id.id or False)
|
||||
self.write(cr, uid, [ids[i]], {'stage_id' : stage_id})
|
||||
|
||||
return res
|
||||
|
||||
crm_claim()
|
||||
|
||||
|
||||
class crm_stage_claim(osv.osv):
|
||||
|
||||
def _get_type_value(self, cr, user, context):
|
||||
list = super(crm_stage_claim, self)._get_type_value(cr, user, context)
|
||||
list.append(('claim','Claim'))
|
||||
return list
|
||||
|
||||
_inherit = "crm.case.stage"
|
||||
_columns = {
|
||||
'type': fields.selection(_get_type_value, 'Type'),
|
||||
}
|
||||
|
||||
|
||||
crm_stage_claim()
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<field name="context">{'type':'claim'}</field>
|
||||
<field name="help">You can create claim stages to categorize the status of every claim entered in the system. The stages define all the steps required for the resolution of a claim.</field>
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem action="crm_claim_stage_act" name="Stages"
|
||||
id="menu_crm_claim_stage_act" parent="menu_config_claim" />
|
||||
|
||||
|
@ -70,18 +70,26 @@
|
|||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Claims">
|
||||
<group colspan="4" col="6">
|
||||
<group>
|
||||
<field name="name" />
|
||||
<field name="date"/>
|
||||
<field name="date_deadline"/>
|
||||
<newline/>
|
||||
|
||||
|
||||
</group>
|
||||
|
||||
<group colspan="4" col="6">
|
||||
|
||||
<field name="user_id"/>
|
||||
<field name="priority"/>
|
||||
<field name="section_id" widget="selection" />
|
||||
|
||||
<group colspan="2" col="4">
|
||||
<field name="stage_id" domain="[('type','=','claim')]"/>
|
||||
<field name="stage_id" domain="[('type','=','claim'),('section_ids', '=', section_id)]"/>
|
||||
<button name="stage_previous" string="" type="object" icon="gtk-go-back" />
|
||||
<button icon="gtk-go-forward" string="" name="stage_next" type="object"/>
|
||||
</group>
|
||||
<newline />
|
||||
<field name="priority"/>
|
||||
<field name="date_deadline"/>
|
||||
</group>
|
||||
<group colspan="4" col="4">
|
||||
<notebook>
|
||||
|
|
|
@ -94,3 +94,19 @@ class crm_fundraising(crm.crm_case, osv.osv):
|
|||
}
|
||||
|
||||
crm_fundraising()
|
||||
|
||||
|
||||
class crm_stage_fundraising(osv.osv):
|
||||
|
||||
def _get_type_value(self, cr, user, context):
|
||||
list = super(crm_stage_fundraising, self)._get_type_value(cr, user, context)
|
||||
list.append(('fundraising','Fundraising'))
|
||||
return list
|
||||
|
||||
_inherit = "crm.case.stage"
|
||||
_columns = {
|
||||
'type': fields.selection(_get_type_value, 'Type'),
|
||||
}
|
||||
|
||||
|
||||
crm_stage_fundraising()
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
<field name="context">{'type':'fundraising'}</field>
|
||||
<field name="help">Create and manage fund raising activity categories you want to be maintained in the system.</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
<menuitem action="crm_fundraising_stage_act"
|
||||
groups="base.group_extended" name="Stages"
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import netsvc
|
||||
from osv import fields,osv
|
||||
|
||||
|
||||
|
|
|
@ -165,11 +165,9 @@ class contentIndex(object):
|
|||
os.write(fd, content)
|
||||
os.close(fd)
|
||||
|
||||
fp = Popen(['file','-b','--mime',fname], shell=False, stdout=PIPE).stdout
|
||||
try:
|
||||
result = fp.read()
|
||||
finally:
|
||||
fp.close()
|
||||
pop = Popen(['file','-b','--mime',fname], shell=False, stdout=PIPE)
|
||||
(result, _) = pop.communicate()
|
||||
|
||||
mime2 = result.split(';')[0]
|
||||
self.__logger.debug('File gave us: %s', mime2)
|
||||
# Note that the temporary file still exists now.
|
||||
|
|
|
@ -82,7 +82,6 @@
|
|||
<group colspan="4" col="4" attrs="{'invisible': [('type','!=','ressource')]}">
|
||||
<field name="ressource_type_id" on_change="onchange_content_id(ressource_type_id)"
|
||||
attrs="{'required': [('type','=','ressource')] }"/>
|
||||
<field name="resource_find_all" groups="base.group_extended" />
|
||||
<newline/>
|
||||
<field name="resource_field" domain="[('model_id','=',ressource_type_id), ('ttype', 'in', ('char', 'selection', 'date', 'datetime'))]"/>
|
||||
<field name="ressource_tree"/>
|
||||
|
@ -93,6 +92,9 @@
|
|||
<field name="ressource_parent_type_id"/>
|
||||
<field name="ressource_id" select="2" readonly="1"/>
|
||||
</group>
|
||||
<group colspan="4" col="2" attrs="{'invisible': [('type','!=','ressource'),('resource_parent_type_id','=',False)]}">
|
||||
<field name="resource_find_all" groups="base.group_extended" />
|
||||
</group>
|
||||
|
||||
</page>
|
||||
<page string="Generated Files" groups="base.group_extended">
|
||||
|
|
|
@ -837,7 +837,9 @@ class node_res_dir(node_class):
|
|||
where.append(('id','=',self.resm_id))
|
||||
|
||||
if name:
|
||||
where.append((self.namefield,'=',name))
|
||||
# The =like character will match underscores against any characters
|
||||
# including the special ones that couldn't exist in a FTP/DAV request
|
||||
where.append((self.namefield,'=like',name.replace('\\','\\\\')))
|
||||
is_allowed = self.check_perms(1)
|
||||
else:
|
||||
is_allowed = self.check_perms(5)
|
||||
|
@ -858,12 +860,22 @@ class node_res_dir(node_class):
|
|||
for bo in obj.browse(cr, uid, resids, context=ctx):
|
||||
if not bo:
|
||||
continue
|
||||
name = getattr(bo, self.namefield)
|
||||
if not name:
|
||||
res_name = getattr(bo, self.namefield)
|
||||
if not res_name:
|
||||
continue
|
||||
# Yes! we can't do better but skip nameless records.
|
||||
|
||||
# Escape the name for characters not supported in filenames
|
||||
res_name = res_name.replace('/','_') # any other weird char?
|
||||
|
||||
if name and (res_name != name):
|
||||
# we have matched _ to any character, but we only meant to match
|
||||
# the special ones.
|
||||
# Eg. 'a_c' will find 'abc', 'a/c', 'a_c', may only
|
||||
# return 'a/c' and 'a_c'
|
||||
continue
|
||||
|
||||
res.append(self.res_obj_class(name, self.dir_id, self, self.context, self.res_model, bo))
|
||||
res.append(self.res_obj_class(res_name, self.dir_id, self, self.context, self.res_model, bo))
|
||||
return res
|
||||
|
||||
def _get_ttag(self,cr):
|
||||
|
@ -896,7 +908,10 @@ class node_res_obj(node_class):
|
|||
self.domain = parent.domain
|
||||
self.displayname = path
|
||||
self.dctx_dict = parent.dctx_dict
|
||||
self.res_find_all = parent.res_find_all
|
||||
if isinstance(parent, node_res_dir):
|
||||
self.res_find_all = parent.res_find_all
|
||||
else:
|
||||
self.res_find_all = False
|
||||
if res_bo:
|
||||
self.res_id = res_bo.id
|
||||
dc2 = self.context.context.copy()
|
||||
|
@ -1016,8 +1031,10 @@ class node_res_obj(node_class):
|
|||
# Directory Structure display in tree structure
|
||||
if self.res_id and directory.ressource_tree:
|
||||
where1 = []
|
||||
if name:
|
||||
where1.append(('name','=like',name.replace('\\','\\\\')))
|
||||
if obj._parent_name in obj.fields_get(cr, uid):
|
||||
where1 = where + [(obj._parent_name, '=', self.res_id)]
|
||||
where1.append((obj._parent_name, '=', self.res_id))
|
||||
namefield = directory.resource_field.name or 'name'
|
||||
resids = obj.search(cr, uid, where1, context=ctx)
|
||||
for bo in obj.browse(cr, uid, resids, context=ctx):
|
||||
|
@ -1026,27 +1043,37 @@ class node_res_obj(node_class):
|
|||
res_name = getattr(bo, namefield)
|
||||
if not res_name:
|
||||
continue
|
||||
res_name = res_name.replace('/', '_')
|
||||
if name and (res_name != name):
|
||||
continue
|
||||
# TODO Revise
|
||||
klass = directory.get_node_class(directory, dynamic=True, context=ctx)
|
||||
res.append(klass(res_name, dir_id=self.dir_id, parent=self, context=self.context, res_model=self.res_model, res_bo=bo))
|
||||
rnode = klass(res_name, dir_id=self.dir_id, parent=self, context=self.context,
|
||||
res_model=self.res_model, res_bo=bo)
|
||||
rnode.res_find_all = self.res_find_all
|
||||
res.append(rnode)
|
||||
|
||||
|
||||
where2 = where + [('parent_id','=',self.dir_id) ]
|
||||
ids = dirobj.search(cr, uid, where2, context=ctx)
|
||||
bo = obj.browse(cr, uid, self.res_id, context=ctx)
|
||||
|
||||
for dirr in dirobj.browse(cr, uid, ids, context=ctx):
|
||||
if name and (name != dirr.name):
|
||||
continue
|
||||
if dirr.type == 'directory':
|
||||
klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
|
||||
res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
|
||||
res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = bo, res_id = self.res_id))
|
||||
elif dirr.type == 'ressource':
|
||||
# child resources can be controlled by properly set dctx
|
||||
klass = dirr.get_node_class(dirr, context=ctx)
|
||||
res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
|
||||
res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id})) # bo?
|
||||
|
||||
fil_obj = dirobj.pool.get('ir.attachment')
|
||||
if self.res_find_all:
|
||||
where2 = where
|
||||
where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
|
||||
# print "where clause for dir_obj", where2
|
||||
where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
|
||||
# print "where clause for dir_obj", where3
|
||||
ids = fil_obj.search(cr, uid, where3, context=ctx)
|
||||
if ids:
|
||||
for fil in fil_obj.browse(cr, uid, ids, context=ctx):
|
||||
|
@ -1057,17 +1084,19 @@ class node_res_obj(node_class):
|
|||
# Get Child Ressource Directories
|
||||
if directory.ressource_type_id and directory.ressource_type_id.id:
|
||||
where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
|
||||
where5 = where4 + [('ressource_id','=',0)]
|
||||
where5 = where4 + ['|', ('ressource_id','=',0), ('ressource_id','=',self.res_id)]
|
||||
dirids = dirobj.search(cr,uid, where5)
|
||||
where5 = where4 + [('ressource_id','=',self.res_id)]
|
||||
dirids = dirids + dirobj.search(cr,uid, where5)
|
||||
for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
|
||||
if dirr.type == 'directory' and not dirr.parent_id:
|
||||
klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
|
||||
res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
|
||||
rnode = klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = bo, res_id = self.res_id)
|
||||
rnode.res_find_all = dirr.resource_find_all
|
||||
res.append(rnode)
|
||||
if dirr.type == 'ressource':
|
||||
klass = dirr.get_node_class(dirr, context=ctx)
|
||||
res.append(klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
|
||||
rnode = klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id})
|
||||
rnode.res_find_all = dirr.resource_find_all
|
||||
res.append(rnode)
|
||||
return res
|
||||
|
||||
def create_child_collection(self, cr, objname):
|
||||
|
@ -1092,7 +1121,8 @@ class node_res_obj(node_class):
|
|||
'name': objname,
|
||||
'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
|
||||
'ressource_id': object2 and object2.id or False,
|
||||
'parent_id' : False
|
||||
'parent_id' : False,
|
||||
'resource_find_all': False,
|
||||
}
|
||||
if (obj and (obj.type in ('directory'))) or not object2:
|
||||
val['parent_id'] = obj and obj.id or False
|
||||
|
|
|
@ -3,9 +3,13 @@
|
|||
import sys
|
||||
import os
|
||||
import glob
|
||||
import time
|
||||
import logging
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
parser = OptionParser()
|
||||
parser.add_option("-q", "--quiet",
|
||||
action="store_false", dest="verbose", default=True,
|
||||
|
@ -15,6 +19,10 @@ parser.add_option("-C", "--content",
|
|||
action="store_true", dest="docontent", default=False,
|
||||
help="Disect content, rather than the file.")
|
||||
|
||||
parser.add_option("--delay",
|
||||
action="store_true", dest="delay", default=False,
|
||||
help="delay after the operation, to inspect child processes")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
import content_index, std_index
|
||||
|
@ -34,9 +42,12 @@ for fname in args:
|
|||
if options.verbose:
|
||||
for line in res[:5]:
|
||||
print line
|
||||
if options.delay:
|
||||
time.sleep(30)
|
||||
except Exception,e:
|
||||
import traceback
|
||||
tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print "Keyboard interrupt"
|
||||
|
||||
#eof
|
||||
|
|
|
@ -35,6 +35,9 @@ import urllib
|
|||
from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
|
||||
from cache import memoize
|
||||
from tools import misc
|
||||
|
||||
from webdav import mk_lock_response
|
||||
|
||||
try:
|
||||
from tools.dict_tools import dict_merge2
|
||||
except ImportError:
|
||||
|
@ -209,18 +212,20 @@ class openerp_dav_handler(dav_interface):
|
|||
self.parent.log_error("Cannot %s: %s", opname, err.strerror)
|
||||
self.parent.log_message("Exc: %s",traceback.format_exc())
|
||||
raise default_exc(err.strerror)
|
||||
except Exception,e:
|
||||
except Exception, e:
|
||||
import traceback
|
||||
if cr: cr.close()
|
||||
self.parent.log_error("Cannot %s: %s", opname, str(e))
|
||||
self.parent.log_message("Exc: %s",traceback.format_exc())
|
||||
raise default_exc("Operation failed")
|
||||
|
||||
#def _get_dav_lockdiscovery(self, uri):
|
||||
# raise DAV_NotFound
|
||||
def _get_dav_lockdiscovery(self, uri):
|
||||
""" We raise that so that the node API is used """
|
||||
raise DAV_NotFound
|
||||
|
||||
#def A_get_dav_supportedlock(self, uri):
|
||||
# raise DAV_NotFound
|
||||
def _get_dav_supportedlock(self, uri):
|
||||
""" We raise that so that the node API is used """
|
||||
raise DAV_NotFound
|
||||
|
||||
def match_prop(self, uri, match, ns, propname):
|
||||
if self.M_NS.has_key(ns):
|
||||
|
@ -346,7 +351,7 @@ class openerp_dav_handler(dav_interface):
|
|||
""" Return the base URI of this request, or even join it with the
|
||||
ajoin path elements
|
||||
"""
|
||||
return self.baseuri+ '/'.join(ajoin)
|
||||
return self.parent.get_baseuri(self) + '/'.join(ajoin)
|
||||
|
||||
@memoize(4)
|
||||
def db_list(self):
|
||||
|
@ -911,6 +916,91 @@ class openerp_dav_handler(dav_interface):
|
|||
cr.close()
|
||||
return result
|
||||
|
||||
def unlock(self, uri, token):
|
||||
""" Unlock a resource from that token
|
||||
|
||||
@return True if unlocked, False if no lock existed, Exceptions
|
||||
"""
|
||||
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
|
||||
if not dbname:
|
||||
if cr: cr.close()
|
||||
raise DAV_Error, 409
|
||||
|
||||
node = self.uri2object(cr, uid, pool, uri2)
|
||||
try:
|
||||
node_fn = node.dav_unlock
|
||||
except AttributeError:
|
||||
# perhaps the node doesn't support locks
|
||||
cr.close()
|
||||
raise DAV_Error(400, 'No locks for this resource')
|
||||
|
||||
res = self._try_function(node_fn, (cr, token), "unlock %s" % uri, cr=cr)
|
||||
cr.commit()
|
||||
cr.close()
|
||||
return res
|
||||
|
||||
def lock(self, uri, lock_data):
|
||||
""" Lock (may create) resource.
|
||||
Data is a dict, may contain:
|
||||
depth, token, refresh, lockscope, locktype, owner
|
||||
"""
|
||||
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
|
||||
created = False
|
||||
if not dbname:
|
||||
if cr: cr.close()
|
||||
raise DAV_Error, 409
|
||||
|
||||
try:
|
||||
node = self.uri2object(cr, uid, pool, uri2[:])
|
||||
except Exception:
|
||||
node = False
|
||||
|
||||
objname = misc.ustr(uri2[-1])
|
||||
|
||||
if not node:
|
||||
dir_node = self.uri2object(cr, uid, pool, uri2[:-1])
|
||||
if not dir_node:
|
||||
cr.close()
|
||||
raise DAV_NotFound('Parent folder not found')
|
||||
|
||||
# We create a new node (file) but with empty data=None,
|
||||
# as in RFC4918 p. 9.10.4
|
||||
node = self._try_function(dir_node.create_child, (cr, objname, None),
|
||||
"create %s" % objname, cr=cr)
|
||||
if not node:
|
||||
cr.commit()
|
||||
cr.close()
|
||||
raise DAV_Error(400, "Failed to create resource")
|
||||
|
||||
created = True
|
||||
|
||||
try:
|
||||
node_fn = node.dav_lock
|
||||
except AttributeError:
|
||||
# perhaps the node doesn't support locks
|
||||
cr.close()
|
||||
raise DAV_Error(400, 'No locks for this resource')
|
||||
|
||||
# Obtain the lock on the node
|
||||
lres, pid, token = self._try_function(node_fn, (cr, lock_data), "lock %s" % objname, cr=cr)
|
||||
|
||||
if not lres:
|
||||
cr.commit()
|
||||
cr.close()
|
||||
raise DAV_Error(423, "Resource already locked")
|
||||
|
||||
assert isinstance(lres, list), 'lres: %s' % repr(lres)
|
||||
|
||||
try:
|
||||
data = mk_lock_response(self, uri, lres)
|
||||
cr.commit()
|
||||
except Exception:
|
||||
cr.close()
|
||||
raise
|
||||
|
||||
cr.close()
|
||||
return created, data, token
|
||||
|
||||
@memoize(CACHE_SIZE)
|
||||
def is_collection(self, uri):
|
||||
""" test if the given uri is a collection """
|
||||
|
|
|
@ -80,6 +80,10 @@ class dav_dir_property(osv.osv):
|
|||
_name = 'document.webdav.dir.property'
|
||||
|
||||
_columns = {
|
||||
'create_date': fields.datetime('Date Created', readonly=True),
|
||||
'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
|
||||
'write_date': fields.datetime('Date Modified', readonly=True),
|
||||
'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
|
||||
'dir_id': fields.many2one('document.directory', 'Directory', required=False, select=1),
|
||||
'namespace': fields.char('Namespace', size=127, required=True),
|
||||
'name': fields.char('Name', size=64, required=True),
|
||||
|
@ -93,4 +97,34 @@ class dav_dir_property(osv.osv):
|
|||
|
||||
dav_dir_property()
|
||||
|
||||
class dav_file_property(osv.osv):
|
||||
""" Arbitrary WebDAV properties, attached to ir.attachments.
|
||||
|
||||
A special case is the locks that can be applied on file nodes.
|
||||
|
||||
There _can_ be properties without a file (RFC?), which means that they
|
||||
globally apply to all the attachments of the present database.
|
||||
|
||||
TODO access permissions, per property.
|
||||
"""
|
||||
_name = 'document.webdav.file.property'
|
||||
|
||||
_columns = {
|
||||
'create_date': fields.datetime('Date Created', readonly=True),
|
||||
'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
|
||||
'write_date': fields.datetime('Date Modified', readonly=True),
|
||||
'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
|
||||
'file_id': fields.many2one('ir.attachment', 'Document', required=False, select=1),
|
||||
'namespace': fields.char('Namespace', size=127, required=True),
|
||||
'name': fields.char('Name', size=64, required=True),
|
||||
'value': fields.text('Value'),
|
||||
'do_subst': fields.boolean('Substitute', required=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'do_subst': False,
|
||||
}
|
||||
|
||||
dav_file_property()
|
||||
|
||||
#eof
|
|
@ -22,14 +22,14 @@
|
|||
|
||||
from document import nodes
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
import time
|
||||
import urllib
|
||||
import uuid
|
||||
try:
|
||||
from tools.dict_tools import dict_filter
|
||||
except ImportError:
|
||||
from document.dict_tools import dict_filter
|
||||
|
||||
import urllib
|
||||
|
||||
|
||||
class node_acl_mixin(object):
|
||||
def _get_dav_owner(self, cr):
|
||||
return self.uuser
|
||||
|
@ -116,6 +116,153 @@ class node_acl_mixin(object):
|
|||
return val
|
||||
return None
|
||||
|
||||
def _dav_lock_hlpr(self, cr, lock_data, par_class, prop_model,
|
||||
prop_ref_field, res_id):
|
||||
""" Helper, which uses the dav properties table for placing locks
|
||||
|
||||
@param lock_data a dictionary of input to this function.
|
||||
@return list of tuples, DAV:activelock _contents_ structure.
|
||||
See webdav.py:class Prop2Xml() for semantics
|
||||
|
||||
Note: although the DAV response shall be an <activelock/>, this
|
||||
function will only return the elements inside the activelock,
|
||||
because the calling function needs to append the <lockroot/> in
|
||||
it. See webdav.py:mk_lock_response()
|
||||
|
||||
In order to reuse code, this function can be called with
|
||||
lock_data['unlock_mode']=True, in order to unlock.
|
||||
|
||||
@return bool in unlock mode, (davstruct, prop_id, token) in lock/refresh,
|
||||
or (False, prop_id, token) if already locked,
|
||||
or (False, False, False) if lock not found to refresh
|
||||
"""
|
||||
assert prop_model
|
||||
assert res_id
|
||||
assert isinstance(lock_data, dict), '%r' % lock_data
|
||||
propobj = self.context._dirobj.pool.get(prop_model)
|
||||
uid = self.context.uid
|
||||
ctx = self.context.context.copy()
|
||||
ctx.update(self.dctx)
|
||||
ctx.update({'uid': uid, 'dbname': self.context.dbname })
|
||||
ctx['node_classname'] = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
|
||||
dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
|
||||
sdomain = [(prop_ref_field, '=', res_id), ('namespace', '=', 'DAV:'),
|
||||
('name','=', 'lockdiscovery')]
|
||||
props_to_delete = []
|
||||
lock_found = False
|
||||
lock_val = None
|
||||
tmout2 = int(lock_data.get('timeout', 3*3600))
|
||||
|
||||
prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
|
||||
if prop_ids:
|
||||
for pbro in propobj.browse(cr, uid, prop_ids, context=ctx):
|
||||
val = pbro.value
|
||||
if pbro.do_subst:
|
||||
if val.startswith("('") and val.endswith(")"):
|
||||
glbls = { 'urlquote': urllib.quote, }
|
||||
val = eval(val, glbls, ctx)
|
||||
else:
|
||||
# all locks should be at "subst" format
|
||||
continue
|
||||
if not (val and isinstance(val, tuple)
|
||||
and val[0:2] == ( 'activelock','DAV:')):
|
||||
# print "Value is not activelock:", val
|
||||
continue
|
||||
|
||||
old_token = False
|
||||
old_owner = False
|
||||
try:
|
||||
# discover the timeout. If anything goes wrong, delete
|
||||
# the lock (cleanup)
|
||||
tmout = False
|
||||
for parm in val[2]:
|
||||
if parm[1] != 'DAV:':
|
||||
continue
|
||||
if parm[0] == 'timeout':
|
||||
if isinstance(parm[2], basestring) \
|
||||
and parm[2].startswith('Second-'):
|
||||
tmout = int(parm[2][7:])
|
||||
elif parm[0] == 'locktoken':
|
||||
if isinstance(parm[2], basestring):
|
||||
old_token = parm[2]
|
||||
elif isinstance(parm[2], tuple) and \
|
||||
parm[2][0:2] == ('href','DAV:'):
|
||||
old_token = parm[2][2]
|
||||
else:
|
||||
# print "Mangled token in DAV property: %r" % parm[2]
|
||||
props_to_delete.append(pbro.id)
|
||||
continue
|
||||
elif parm[0] == 'owner':
|
||||
old_owner = parm[2] # not used yet
|
||||
if tmout:
|
||||
mdate = pbro.write_date or pbro.create_date
|
||||
mdate = time.mktime(time.strptime(mdate,'%Y-%m-%d %H:%M:%S'))
|
||||
if mdate + tmout < time.time():
|
||||
props_to_delete.append(pbro.id)
|
||||
continue
|
||||
else:
|
||||
props_to_delete.append(pbro.id)
|
||||
continue
|
||||
except ValueError:
|
||||
props_to_delete.append(pbro.id)
|
||||
continue
|
||||
|
||||
# A valid lock is found here
|
||||
if lock_data.get('refresh', False):
|
||||
if old_token != lock_data.get('token'):
|
||||
continue
|
||||
# refresh mode. Just touch anything and the ORM will update
|
||||
# the write uid+date, won't it?
|
||||
# Note: we don't update the owner, because incoming refresh
|
||||
# wouldn't have a body, anyway.
|
||||
propobj.write(cr, uid, [pbro.id,], { 'name': 'lockdiscovery'})
|
||||
elif lock_data.get('unlock_mode', False):
|
||||
if old_token != lock_data.get('token'):
|
||||
continue
|
||||
props_to_delete.append(pbro.id)
|
||||
|
||||
lock_found = pbro.id
|
||||
lock_val = val
|
||||
|
||||
if tmout2 > 3*3600: # 3 hours maximum
|
||||
tmout2 = 3*3600
|
||||
elif tmout2 < 300:
|
||||
# 5 minutes minimum, but an unlock request can always
|
||||
# break it at any time. Ensures no negative values, either.
|
||||
tmout2 = 300
|
||||
|
||||
if props_to_delete:
|
||||
# explicitly delete, as admin, any of the ids we have identified.
|
||||
propobj.unlink(cr, 1, props_to_delete)
|
||||
|
||||
if lock_data.get('unlock_mode', False):
|
||||
return lock_found and True
|
||||
elif (not lock_found) and not (lock_data.get('refresh', False)):
|
||||
# Create a new lock, attach and return it.
|
||||
new_token = uuid.uuid4().urn
|
||||
lock_val = ('activelock', 'DAV:',
|
||||
[ ('locktype', 'DAV:', (lock_data.get('locktype',False) or 'write','DAV:')),
|
||||
('lockscope', 'DAV:', (lock_data.get('lockscope',False) or 'exclusive','DAV:')),
|
||||
# ? ('depth', 'DAV:', lock_data.get('depth','0') ),
|
||||
('timeout','DAV:', 'Second-%d' % tmout2),
|
||||
('locktoken', 'DAV:', ('href', 'DAV:', new_token)),
|
||||
# ('lockroot', 'DAV: ..., we don't store that, appended by caller
|
||||
])
|
||||
new_owner = lock_data.get('lockowner',False) or ctx.get('username', False)
|
||||
if new_owner:
|
||||
lock_val[2].append( ('owner', 'DAV:', new_owner) )
|
||||
prop_id = propobj.create(cr, uid, { prop_ref_field: res_id,
|
||||
'namespace': 'DAV:', 'name': 'lockdiscovery',
|
||||
'do_subst': True, 'value': repr(lock_val) })
|
||||
return (lock_val[2], prop_id, new_token )
|
||||
elif not lock_found: # and refresh
|
||||
return (False, False, False)
|
||||
elif lock_found and not lock_data.get('refresh', False):
|
||||
# already locked
|
||||
return (False, lock_found, old_token)
|
||||
else:
|
||||
return (lock_val[2], lock_found, old_token )
|
||||
|
||||
class node_dir(node_acl_mixin, nodes.node_dir):
|
||||
""" override node_dir and add DAV functionality
|
||||
"""
|
||||
|
@ -141,7 +288,8 @@ class node_dir(node_acl_mixin, nodes.node_dir):
|
|||
class node_file(node_acl_mixin, nodes.node_file):
|
||||
DAV_PROPS = { "DAV:": ('owner', 'group',
|
||||
'supported-privilege-set',
|
||||
'current-user-privilege-set'),
|
||||
'current-user-privilege-set',
|
||||
),
|
||||
}
|
||||
DAV_M_NS = { "DAV:" : '_get_dav',}
|
||||
http_options = { 'DAV': ['access-control', ] }
|
||||
|
@ -152,10 +300,45 @@ class node_file(node_acl_mixin, nodes.node_file):
|
|||
|
||||
def get_dav_props(self, cr):
|
||||
return self._get_dav_props_hlpr(cr, nodes.node_dir,
|
||||
None, 'file_id', self.file_id)
|
||||
#'document.webdav.dir.property', 'dir_id', self.dir_id)
|
||||
'document.webdav.file.property', 'file_id', self.file_id)
|
||||
|
||||
#def get_dav_eprop(self, cr, ns, prop):
|
||||
def dav_lock(self, cr, lock_data):
|
||||
""" Locks or unlocks the node, using DAV semantics.
|
||||
|
||||
Unlocking will be done when lock_data['unlock_mode'] == True
|
||||
|
||||
See _dav_lock_hlpr() for calling details.
|
||||
|
||||
It is fundamentally OK to use this function from non-DAV endpoints,
|
||||
but they will all have to emulate the tuple-in-list structure of
|
||||
the DAV lock data. RFC if this translation should be done inside
|
||||
the _dav_lock_hlpr (to ease other protocols).
|
||||
"""
|
||||
return self._dav_lock_hlpr(cr, lock_data, nodes.node_file,
|
||||
'document.webdav.file.property', 'file_id', self.file_id)
|
||||
|
||||
def dav_unlock(self, cr, token):
|
||||
"""Releases the token lock held for the node
|
||||
|
||||
This is a utility complement of dav_lock()
|
||||
"""
|
||||
lock_data = { 'token': token, 'unlock_mode': True }
|
||||
return self._dav_lock_hlpr(cr, lock_data, nodes.node_file,
|
||||
'document.webdav.file.property', 'file_id', self.file_id)
|
||||
|
||||
def get_dav_eprop(self, cr, ns, prop):
|
||||
if ns == 'DAV:' and prop == 'supportedlock':
|
||||
return [ ('lockentry', 'DAV:',
|
||||
[ ('lockscope','DAV:', ('shared', 'DAV:')),
|
||||
('locktype','DAV:', ('write', 'DAV:')),
|
||||
]),
|
||||
('lockentry', 'DAV:',
|
||||
[ ('lockscope','DAV:', ('exclusive', 'DAV:')),
|
||||
('locktype','DAV:', ('write', 'DAV:')),
|
||||
] )
|
||||
]
|
||||
return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_file,
|
||||
'document.webdav.file.property', 'file_id', self.file_id)
|
||||
|
||||
class node_database(nodes.node_database):
|
||||
def get_dav_resourcetype(self, cr):
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
"access_webdav_dir_property_all","webdav.dir.property all","model_document_webdav_dir_property",,1,0,0,0
|
||||
"access_webdav_dir_property_group_doc_manager","webdav.dir.property document manager","model_document_webdav_dir_property","base.group_system",1,1,1,1
|
||||
"access_webdav_dir_property_group_system","webdav.dir.property group system","model_document_webdav_dir_property","base.group_system",1,1,1,1
|
||||
"access_webdav_file_property_all","webdav.file.property all","model_document_webdav_file_property",,1,1,1,1
|
||||
|
|
|
|
@ -1,12 +1,17 @@
|
|||
-
|
||||
In order to test the document_ftp functionality
|
||||
In order to test the document_webdav functionality
|
||||
-
|
||||
I open the HTTP port and perform an OPTIONS request to the server
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_webdav import test_davclient as te
|
||||
reload(te) # reload..
|
||||
dc = te.DAVClient()
|
||||
dc = te.DAVClient(timeout=2.0)
|
||||
# have a small timeout, enough for any heavily-loaded test server to
|
||||
# respond, but small so that this test won't block further loading.
|
||||
# Don't catch the exception, so that the whole YAML test will abort
|
||||
# if the WebDAV service is not available (eg. during an upgrade from
|
||||
# command line).
|
||||
dc.gd_options()
|
||||
dc.get_creds(self, cr, uid)
|
||||
dc.gd_options(path=cr.dbname, expect={'DAV': ['1',]})
|
||||
|
|
|
@ -324,7 +324,7 @@ class DAVClient(object):
|
|||
"""An instance of a WebDAV client, connected to the OpenERP server
|
||||
"""
|
||||
|
||||
def __init__(self, user=None, passwd=None, dbg=0, use_ssl=False, useragent=False):
|
||||
def __init__(self, user=None, passwd=None, dbg=0, use_ssl=False, useragent=False, timeout=None):
|
||||
if use_ssl:
|
||||
self.host = config.get_misc('httpsd', 'interface', False)
|
||||
self.port = config.get_misc('httpsd', 'port', 8071)
|
||||
|
@ -346,6 +346,7 @@ class DAVClient(object):
|
|||
self.user = user
|
||||
self.passwd = passwd
|
||||
self.dbg = dbg
|
||||
self.timeout = timeout or 5.0 # seconds, tests need to respond pretty fast!
|
||||
self.hdrs = {}
|
||||
if useragent:
|
||||
self.set_useragent(useragent)
|
||||
|
@ -386,7 +387,7 @@ class DAVClient(object):
|
|||
dbg = self.dbg
|
||||
hdrs.update(self.hdrs)
|
||||
log.debug("Getting %s http://%s:%d/%s", method, self.host, self.port, path)
|
||||
conn = httplib.HTTPConnection(self.host, port=self.port)
|
||||
conn = httplib.HTTPConnection(self.host, port=self.port, timeout=self.timeout)
|
||||
conn.set_debuglevel(dbg)
|
||||
if not path:
|
||||
path = "/index.html"
|
||||
|
|
|
@ -268,6 +268,39 @@ def mk_propname_response(self,uri,propnames,doc):
|
|||
PROPFIND.mk_prop_response = mk_prop_response
|
||||
PROPFIND.mk_propname_response = mk_propname_response
|
||||
|
||||
def mk_lock_response(self, uri, props):
|
||||
""" Prepare the data response to a DAV LOCK command
|
||||
|
||||
This function is here, merely to be in the same file as the
|
||||
ones above, that have similar code.
|
||||
"""
|
||||
doc = domimpl.createDocument('DAV:', "D:prop", None)
|
||||
ms = doc.documentElement
|
||||
ms.setAttribute("xmlns:D", "DAV:")
|
||||
# ms.tagName = 'D:multistatus'
|
||||
namespaces = []
|
||||
nsnum = 0
|
||||
propgen = Prop2xml(doc, namespaces, nsnum)
|
||||
# write href information
|
||||
uparts=urlparse.urlparse(uri)
|
||||
fileloc=uparts[2]
|
||||
if isinstance(fileloc, unicode):
|
||||
fileloc = fileloc.encode('utf-8')
|
||||
davpath = self.parent.get_davpath()
|
||||
if uparts[0] and uparts[1]:
|
||||
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
|
||||
else:
|
||||
# When the request has been relative, we don't have enough data to
|
||||
# reply with absolute url here.
|
||||
hurl = '%s%s' % (davpath, urllib.quote(fileloc))
|
||||
|
||||
props.append( ('lockroot', 'DAV:', ('href', 'DAV:', (hurl))))
|
||||
pld = doc.createElement('D:lockdiscovery')
|
||||
ms.appendChild(pld)
|
||||
propgen._prop_child(pld, 'DAV:', 'activelock', props)
|
||||
|
||||
return doc.toxml(encoding="utf-8")
|
||||
|
||||
super_create_prop = REPORT.create_prop
|
||||
|
||||
def create_prop(self):
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright P. Christeas <p_christ@hol.gr> 2008,2009
|
||||
# Copyright P. Christeas <p_christ@hol.gr> 2008-2010
|
||||
#
|
||||
# Disclaimer: Many of the functions below borrow code from the
|
||||
# python-webdav library (http://code.google.com/p/pywebdav/ ),
|
||||
# which they import and override to suit OpenERP functionality.
|
||||
# python-webdav was written by: Simon Pamies <s.pamies@banality.de>
|
||||
# Christian Scholz <mrtopf@webdav.de>
|
||||
# Vince Spicer <vince@vince.ca>
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsability of assessing all potential
|
||||
|
@ -38,7 +44,9 @@ import urllib
|
|||
import re
|
||||
from string import atoi
|
||||
from DAV.errors import *
|
||||
from DAV.utils import IfParser, TagList
|
||||
# from DAV.constants import DAV_VERSION_1, DAV_VERSION_2
|
||||
from xml.dom import minidom
|
||||
|
||||
khtml_re = re.compile(r' KHTML/([0-9\.]+) ')
|
||||
|
||||
|
@ -103,6 +111,7 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
|
|||
self.headers['Destination'] = up.path[len(self.davpath):]
|
||||
else:
|
||||
raise DAV_Forbidden("Not allowed to copy/move outside webdav path")
|
||||
# TODO: locks
|
||||
DAVRequestHandler.copymove(self, CLASS)
|
||||
|
||||
def get_davpath(self):
|
||||
|
@ -262,6 +271,139 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
|
|||
except DAV_Error, (ec, dd):
|
||||
return self.send_status(ec)
|
||||
|
||||
def do_UNLOCK(self):
|
||||
""" Unlocks given resource """
|
||||
|
||||
dc = self.IFACE_CLASS
|
||||
self.log_message('UNLOCKing resource %s' % self.headers)
|
||||
|
||||
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
|
||||
uri = urllib.unquote(uri)
|
||||
|
||||
token = self.headers.get('Lock-Token', False)
|
||||
if token:
|
||||
token = token.strip()
|
||||
if token[0] == '<' and token[-1] == '>':
|
||||
token = token[1:-1]
|
||||
else:
|
||||
token = False
|
||||
|
||||
if not token:
|
||||
return self.send_status(400, 'Bad lock token')
|
||||
|
||||
try:
|
||||
res = dc.unlock(uri, token)
|
||||
except DAV_Error, (ec, dd):
|
||||
return self.send_status(ec, dd)
|
||||
|
||||
if res == True:
|
||||
self.send_body(None, '204', 'OK', 'Resource unlocked.')
|
||||
else:
|
||||
# We just differentiate the description, for debugging purposes
|
||||
self.send_body(None, '204', 'OK', 'Resource not locked.')
|
||||
|
||||
def do_LOCK(self):
|
||||
""" Attempt to place a lock on the given resource.
|
||||
"""
|
||||
|
||||
dc = self.IFACE_CLASS
|
||||
lock_data = {}
|
||||
|
||||
self.log_message('LOCKing resource %s' % self.headers)
|
||||
|
||||
body = None
|
||||
if self.headers.has_key('Content-Length'):
|
||||
l = self.headers['Content-Length']
|
||||
body = self.rfile.read(atoi(l))
|
||||
|
||||
depth = self.headers.get('Depth', 'infinity')
|
||||
|
||||
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
|
||||
uri = urllib.unquote(uri)
|
||||
self.log_message('do_LOCK: uri = %s' % uri)
|
||||
|
||||
ifheader = self.headers.get('If')
|
||||
|
||||
if ifheader:
|
||||
ldif = IfParser(ifheader)
|
||||
if isinstance(ldif, list):
|
||||
if len(ldif) !=1 or (not isinstance(ldif[0], TagList)) \
|
||||
or len(ldif[0].list) != 1:
|
||||
raise DAV_Error(400, "Cannot accept multiple tokens")
|
||||
ldif = ldif[0].list[0]
|
||||
if ldif[0] == '<' and ldif[-1] == '>':
|
||||
ldif = ldif[1:-1]
|
||||
|
||||
lock_data['token'] = ldif
|
||||
|
||||
if not body:
|
||||
lock_data['refresh'] = True
|
||||
else:
|
||||
lock_data['refresh'] = False
|
||||
lock_data.update(self._lock_unlock_parse(body))
|
||||
|
||||
if lock_data['refresh'] and not lock_data.get('token', False):
|
||||
raise DAV_Error(400, 'Lock refresh must specify token')
|
||||
|
||||
lock_data['depth'] = depth
|
||||
|
||||
try:
|
||||
created, data, lock_token = dc.lock(uri, lock_data)
|
||||
except DAV_Error, (ec, dd):
|
||||
return self.send_status(ec, dd)
|
||||
|
||||
headers = {}
|
||||
if not lock_data['refresh']:
|
||||
headers['Lock-Token'] = '<%s>' % lock_token
|
||||
|
||||
if created:
|
||||
self.send_body(data, '201', 'Created', ctype='text/xml', headers=headers)
|
||||
else:
|
||||
self.send_body(data, '200', 'OK', ctype='text/xml', headers=headers)
|
||||
|
||||
def _lock_unlock_parse(self, body):
|
||||
# Override the python-webdav function, with some improvements
|
||||
# Unlike the py-webdav one, we also parse the owner minidom elements into
|
||||
# pure pythonic struct.
|
||||
doc = minidom.parseString(body)
|
||||
|
||||
data = {}
|
||||
owners = []
|
||||
for info in doc.getElementsByTagNameNS('DAV:', 'lockinfo'):
|
||||
for scope in info.getElementsByTagNameNS('DAV:', 'lockscope'):
|
||||
for scc in scope.childNodes:
|
||||
if scc.nodeType == info.ELEMENT_NODE \
|
||||
and scc.namespaceURI == 'DAV:':
|
||||
data['lockscope'] = scc.localName
|
||||
break
|
||||
for ltype in info.getElementsByTagNameNS('DAV:', 'locktype'):
|
||||
for ltc in ltype.childNodes:
|
||||
if ltc.nodeType == info.ELEMENT_NODE \
|
||||
and ltc.namespaceURI == 'DAV:':
|
||||
data['locktype'] = ltc.localName
|
||||
break
|
||||
for own in info.getElementsByTagNameNS('DAV:', 'owner'):
|
||||
for ono in own.childNodes:
|
||||
if ono.nodeType == info.TEXT_NODE:
|
||||
if ono.data:
|
||||
owners.append(ono.data)
|
||||
elif ono.nodeType == info.ELEMENT_NODE \
|
||||
and ono.namespaceURI == 'DAV:' \
|
||||
and ono.localName == 'href':
|
||||
href = ''
|
||||
for hno in ono.childNodes:
|
||||
if hno.nodeType == info.TEXT_NODE:
|
||||
href += hno.data
|
||||
owners.append(('href','DAV:', href))
|
||||
|
||||
if len(owners) == 1:
|
||||
data['lockowner'] = owners[0]
|
||||
elif not owners:
|
||||
pass
|
||||
else:
|
||||
data['lockowner'] = owners
|
||||
return data
|
||||
|
||||
from service.http_server import reg_http_service,OpenERPAuthProvider
|
||||
|
||||
class DAVAuthProvider(OpenERPAuthProvider):
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<field name="model">document.webdav.dir.property</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Document storage">
|
||||
<search string="Search Document properties">
|
||||
<field name="name" />
|
||||
<field name="namespace" />
|
||||
<newline/>
|
||||
|
@ -92,7 +92,68 @@
|
|||
</page>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- File properties -->
|
||||
<record model="ir.ui.view" id="view_file_props_form">
|
||||
<field name="name">document.webdav.file.property.form</field>
|
||||
<field name="model">document.webdav.file.property</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Properties">
|
||||
<field name="namespace"/>
|
||||
<field name="name"/>
|
||||
<newline />
|
||||
<field name="file_id" />
|
||||
<field name="do_subst" />
|
||||
<newline />
|
||||
<field name="value" colspan="4" />
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_file_props_tree">
|
||||
<field name="name">document.webdav.file.property.tree</field>
|
||||
<field name="model">document.webdav.file.property</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Properties" toolbar="1">
|
||||
<field name="file_id" />
|
||||
<field name="namespace"/>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_file_props_filter" model="ir.ui.view">
|
||||
<field name="name">Search View: File DAV properties</field>
|
||||
<field name="model">document.webdav.file.property</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Document properties">
|
||||
<field name="name" />
|
||||
<field name="namespace" />
|
||||
<newline/>
|
||||
<group expand="0" string="Group By..." groups="base.group_extended">
|
||||
<filter string="Document" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'file_id'}"/>
|
||||
<filter string="Namespace" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'namespace'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_file_props_form">
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">document.webdav.file.property</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="view_file_props_filter"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="DAV properties for documents"
|
||||
action="action_file_props_form"
|
||||
id="menu_file_props"
|
||||
groups="base.group_no_one"
|
||||
parent="document.menu_document_management_configuration"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
-
|
||||
!python {model: hr.sign.in.project}: |
|
||||
uid = ref('test_timesheet_user1')
|
||||
self.check_state(cr, uid, [ref("hr_employee_fracline1")], {"active_ids": [ref("hr_timesheet.menu_hr_timesheet_sign_in")]})
|
||||
self.check_state(cr, uid, [ref("hr_employee_fracline1")], {"active_ids": [ref("hr_timesheet.action_hr_timesheet_sign_in")]})
|
||||
-
|
||||
I select start date and Perform start work on project.
|
||||
-
|
||||
|
@ -110,7 +110,7 @@
|
|||
!python {model: hr.sign.in.project}: |
|
||||
uid = ref('test_timesheet_user1')
|
||||
ids = self.search(cr, uid, [('emp_id', '=', ref('hr_employee_fracline1')),('name', '=', 'Francline')])
|
||||
self.check_state(cr, uid, ids, {"active_ids": [ref("hr_timesheet.menu_hr_timesheet_sign_in")]
|
||||
self.check_state(cr, uid, ids, {"active_ids": [ref("hr_timesheet.action_hr_timesheet_sign_in")]
|
||||
})
|
||||
|
||||
-
|
||||
|
|
|
@ -51,12 +51,6 @@
|
|||
<field name="help">Employees can encode their time spent on the different projects. A project is an analytic account and the time spent on a project generate costs on the analytic account. This feature allows to record at the same time the attendance and the timesheet.</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="action_hr_timesheet_sign_in"
|
||||
id="menu_hr_timesheet_sign_in"
|
||||
groups="base.group_extended"
|
||||
parent="hr_attendance.menu_hr_attendance"
|
||||
sequence="5" />
|
||||
|
||||
<record id="view_hr_timesheet_sign_out" model="ir.ui.view">
|
||||
<field name="name">hr.sign.out.project.form</field>
|
||||
<field name="model">hr.sign.out.project</field>
|
||||
|
|
|
@ -63,7 +63,6 @@ class hr_timesheet_invoice_create(osv.osv_memory):
|
|||
if context is None:
|
||||
context = {}
|
||||
result = mod_obj._get_id(cr, uid, 'account', 'view_account_invoice_filter')
|
||||
res = mod_obj.read(cr, uid, result, ['res_id'], context=context)
|
||||
data = self.read(cr, uid, ids, [], context=context)[0]
|
||||
|
||||
account_ids = {}
|
||||
|
|
|
@ -3,7 +3,17 @@
|
|||
<data>
|
||||
|
||||
<!-- Partners inherited form -->
|
||||
|
||||
<record id="base.view_crm_partner_info_History" model="ir.ui.view">
|
||||
<field name="name">res.partner.crm.history.inherit1</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form/notebook/page[@string='History']" position="attributes">
|
||||
<attribute name="invisible">False</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_emails_partner_info_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.emails.info.inherit</field>
|
||||
<field name="model">res.partner</field>
|
||||
|
|
|
@ -3,7 +3,17 @@
|
|||
<data>
|
||||
|
||||
<!-- Partners inherited form -->
|
||||
|
||||
<record id="base.view_crm_partner_info_History" model="ir.ui.view">
|
||||
<field name="name">res.partner.crm.history.inherit1</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form/notebook/page[@string='History']" position="attributes">
|
||||
<attribute name="invisible">False</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_workitem_partner_info_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.workitem.info.inherit</field>
|
||||
<field name="model">res.partner</field>
|
||||
|
|
|
@ -39,7 +39,7 @@ class outlook_installer(osv.osv_memory):
|
|||
_columns = {
|
||||
'name':fields.char('File name', size=34),
|
||||
'doc_name':fields.char('File name', size=64),
|
||||
'outlook':fields.boolean('Outlook Plug-in ', help="Allows you to select an object that you’d like to add to your email and its attachments."),
|
||||
'outlook':fields.boolean('Outlook Plug-in ', help="Allows you to select an object that you would like to add to your email and its attachments."),
|
||||
'plugin_file':fields.binary('Outlook Plug-in', readonly=True, help="outlook plug-in file. Save as this file and install this plug-in in outlook."),
|
||||
'doc_file':fields.char('Installation Manual',size="264",help="The documentation file :- how to install Outlook Plug-in.", readonly=True),
|
||||
'description':fields.text('Description', readonly=True)
|
||||
|
|
|
@ -175,6 +175,12 @@ class OutlookAddin:
|
|||
toolbar = bars.Item("Standard")
|
||||
openerp_bar = bars.Item('Open ERP')
|
||||
|
||||
item = openerp_bar.Controls.Add(Type = constants.msoControlButton, Temporary = True)
|
||||
item = self.toolbarButtonConfig = DispatchWithEvents(item, Configuration)
|
||||
item.Caption = "Configuration"
|
||||
item.TooltipText = "Click to configure OpenERP."
|
||||
item.Enabled = True
|
||||
|
||||
item = openerp_bar.Controls.Add(Type=constants.msoControlButton, Temporary=True)
|
||||
# Hook events for the item
|
||||
item = self.toolbarButton = DispatchWithEvents(item, ArchiveEvent)
|
||||
|
|
|
@ -705,22 +705,18 @@ def GetSearchText(txtProcessor,*args):
|
|||
b = check()
|
||||
if not b:
|
||||
return
|
||||
|
||||
search_box = txtProcessor.GetControl()
|
||||
global search_text
|
||||
if txtProcessor.init_done:
|
||||
win32gui.SendMessage(search_box, win32con.WM_SETTEXT, 0,search_text)
|
||||
return
|
||||
|
||||
# Get the selected mail and set the default value for search_text_control to mail.SenderEmailAddress
|
||||
ex = txtProcessor.window.manager.outlook.ActiveExplorer()
|
||||
assert ex.Selection.Count == 1
|
||||
mail = ex.Selection.Item(1)
|
||||
try:
|
||||
search_text = ustr(mail.SenderEmailAddress).encode('iso-8859-1')
|
||||
except Exception:
|
||||
pass
|
||||
win32gui.SendMessage(search_box, win32con.WM_SETTEXT, 0, search_text)
|
||||
global objects_with_match
|
||||
list_hwnd = win32gui.GetDlgItem(txtProcessor.window.hwnd, txtProcessor.other_ids[1])
|
||||
objects_with_match = NewConn.SearchPartners()
|
||||
setList(list_hwnd)
|
||||
except Exception,e:
|
||||
msg=getMessage(e)
|
||||
win32ui.MessageBox('Document can not be loaded.\n'+str(e), "Push", flag_error)
|
||||
txtProcessor.init_done=True
|
||||
|
||||
def SetNameColumn(listProcessor,*args):
|
||||
|
@ -1406,14 +1402,11 @@ def OpenPartnerForm(txtProcessor,*args):
|
|||
txtProcessor.init_done=True
|
||||
return
|
||||
try:
|
||||
linktopartner = "http://"+web_server+":"+str(web_server_port)+"/openerp/form/view?model=res.partner&id="+str(vals)
|
||||
|
||||
import urllib
|
||||
next = urllib.urlencode({'next' : '/openerp/form/view?model=res.partner&id=' +str(vals) })
|
||||
weburl = 'http://'+web_server+':'+str(web_server_port)+'/'
|
||||
linktopartner = weburl + '?' + next
|
||||
win32gui.SendMessage(partner_link, win32con.WM_SETTEXT, 0, str(linktopartner))
|
||||
# import urllib
|
||||
# encode = urllib.urlencode('/openerp/form/view?model=res.partner&id='+str(vals))
|
||||
# weburl = 'http://'+web_server+':'+str(web_server_port)
|
||||
# linktopartner = weburl + "?next=" + encode
|
||||
# win32gui.SendMessage(partner_link, win32con.WM_SETTEXT, 0, linktopartner)
|
||||
except Exception,e:
|
||||
win32ui.MessageBox("Error While Opening Partner.\n"+str(e),"Open Partner", flag_error)
|
||||
webbrowser.open_new(linktopartner)
|
||||
|
@ -1469,7 +1462,10 @@ def SerachOpenDocuemnt(txtProcessor,*args):
|
|||
txtProcessor.init_done=True
|
||||
return
|
||||
try:
|
||||
linktodoc = "http:\\\\" +web_server+ ":"+str(web_server_port)+"\\openerp\\form\\view?model="+vals[0][1]+"&id="+str(vals[1][1])
|
||||
import urllib
|
||||
next = urllib.urlencode({'next' : '/openerp/form/view?model='+vals[0][1]+'&id='+str(vals[1][1])})
|
||||
weburl = 'http://'+web_server+':'+str(web_server_port)+'/'
|
||||
linktodoc = weburl + '?' + next
|
||||
win32gui.SendMessage(link_box, win32con.WM_SETTEXT, 0, str(linktodoc))
|
||||
except Exception,e:
|
||||
win32ui.MessageBox("Error While Opening Document.\n"+str(e),"Open Document", flag_error)
|
||||
|
@ -1695,12 +1691,13 @@ dialog_map = {
|
|||
(CommandButtonProcessor, "ID_SEARCH ID_SEARCH_TEXT IDC_NAME_LIST", SearchObjectsForText,()),
|
||||
(GroupProcessor, "IDC_STATIC_GROUP", setCheckList, ()),
|
||||
(CSComboProcessor, "ID_ATT_METHOD_DROPDOWNLIST", GetConn,()),
|
||||
(TextProcessor, "ID_SEARCH_TEXT", GetSearchText, ()),
|
||||
(DialogCommand, "ID_CREATE_CONTACT ID_SEARCH_TEXT", "IDD_NEW_CONTACT_DIALOG", set_search_text, ()),
|
||||
(CloseButtonProcessor, "IDCANCEL"),
|
||||
(CommandButtonProcessor, "ID_MAKE_ATTACHMENT IDC_NAME_LIST IDD_SYNC", MakeAttachment, ()),
|
||||
(CommandButtonProcessor, "ID_CREATE_CASE ID_ATT_METHOD_DROPDOWNLIST IDC_NAME_LIST IDD_SYNC", CreateCase, ()),
|
||||
(ListBoxProcessor, "IDC_NAME_LIST", SetNameColumn, ())
|
||||
(ListBoxProcessor, "IDC_NAME_LIST", SetNameColumn, ()),
|
||||
(TextProcessor, "ID_SEARCH_TEXT ID_SEARCH_TEXT IDC_NAME_LIST", GetSearchText, ()),
|
||||
|
||||
),
|
||||
|
||||
"IDD_NEW_CONTACT_DIALOG" : (
|
||||
|
|
|
@ -59,7 +59,7 @@ class XMLRpcConn(object):
|
|||
'ArchiveToOpenERP', 'IsCRMInstalled', 'GetPartners', 'GetObjectItems', \
|
||||
'CreateCase', 'MakeAttachment', 'CreateContact', 'CreatePartner', 'getitem', 'setitem', \
|
||||
'SearchPartnerDetail', 'WritePartnerValues', 'GetAllState', 'GetAllCountry', 'SearchPartner', 'SearchEmailResources', \
|
||||
'GetCountry', 'GetStates', 'FindCountryForState','CreateEmailAttachment']
|
||||
'GetCountry', 'GetStates', 'FindCountryForState','CreateEmailAttachment','SearchPartners']
|
||||
_reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
|
||||
_reg_clsid_ = "{C6399AFD-763A-400F-8191-7F9D0503CAE2}"
|
||||
_reg_progid_ = "Python.OpenERP.XMLRpcConn"
|
||||
|
@ -550,3 +550,14 @@ class XMLRpcConn(object):
|
|||
res['res_id'] = obj_id
|
||||
id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.attachment','create',res)
|
||||
return id
|
||||
|
||||
def SearchPartners(self):
|
||||
res = []
|
||||
conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
|
||||
obj = 'res.partner'
|
||||
ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,obj,'search',[])
|
||||
recs = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,obj,'read',ids,['id','name'])
|
||||
for rec in recs:
|
||||
name = ustr(rec['name'])
|
||||
res.append((obj,rec['id'],name,obj))
|
||||
return res
|
||||
|
|
|
@ -988,7 +988,6 @@ class pos_order_line(osv.osv):
|
|||
else:
|
||||
res[line.id][f] = line.price_unit * line.qty
|
||||
elif f == 'price_subtotal_incl':
|
||||
tax_amount = 0.0
|
||||
taxes = [t for t in line.product_id.taxes_id]
|
||||
if line.qty == 0.0:
|
||||
res[line.id][f] = 0.0
|
||||
|
|
|
@ -129,6 +129,7 @@ class project(osv.osv):
|
|||
'priority': fields.integer('Sequence', help="Gives the sequence order when displaying a list of task"),
|
||||
'warn_manager': fields.boolean('Warn Manager', help="If you check this field, the project manager will receive a request each time a task is completed by his team.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
|
||||
'members': fields.many2many('res.users', 'project_user_rel', 'project_id', 'uid', 'Project Members', help="Project's member. Not used in any computation, just for information purpose.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
|
||||
'parent_id': fields.many2one('project.project', 'Parent Project'),
|
||||
'tasks': fields.one2many('project.task', 'project_id', "Project tasks"),
|
||||
'planned_hours': fields.function(_progress_rate, multi="progress", method=True, string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.",
|
||||
store = {
|
||||
|
@ -342,7 +343,16 @@ class task(osv.osv):
|
|||
|
||||
def onchange_planned(self, cr, uid, ids, planned = 0.0, effective = 0.0):
|
||||
return {'value':{'remaining_hours': planned - effective}}
|
||||
|
||||
|
||||
def onchange_project(self, cr, uid, id, project_id):
|
||||
if not project_id:
|
||||
return {}
|
||||
data = self.pool.get('project.project').browse(cr, uid, [project_id])
|
||||
partner_id=data and data[0].parent_id.partner_id
|
||||
if partner_id:
|
||||
return {'value':{'partner_id':partner_id.id}}
|
||||
return {}
|
||||
|
||||
def _default_project(self, cr, uid, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
@ -358,7 +368,7 @@ class task(osv.osv):
|
|||
default['active'] = True
|
||||
default['type_id'] = False
|
||||
if not default.get('name', False):
|
||||
default['name'] = self.browse(cr, uid, id, context=context).name + _(' (copy)')
|
||||
default['name'] = self.browse(cr, uid, id, context=context).name
|
||||
return super(task, self).copy_data(cr, uid, id, default, context)
|
||||
|
||||
def _check_dates(self, cr, uid, ids, context=None):
|
||||
|
@ -476,6 +486,7 @@ class task(osv.osv):
|
|||
# Override view according to the company definition
|
||||
#
|
||||
|
||||
|
||||
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
users_obj = self.pool.get('res.users')
|
||||
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
|
||||
<!--
|
||||
Resource: project.project
|
||||
-->
|
||||
|
||||
<record id="all_projects_account" model="account.analytic.account">
|
||||
<field name="name">Projects</field>
|
||||
<field name="code">3</field>
|
||||
</record>
|
||||
|
||||
<function eval="('default',False,'parent_id', [('project.project', False)], all_projects_account, True, False, False, False, True)" id="parent_project_default_set" model="ir.values" name="set"/>
|
||||
-->
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -241,6 +241,11 @@
|
|||
</record>
|
||||
|
||||
<!-- Projects -->
|
||||
<record id="all_projects_account" model="project.project">
|
||||
<field name="name">Projects</field>
|
||||
<field name="code">3</field>
|
||||
</record>
|
||||
|
||||
<record id="project_project_9" model="project.project">
|
||||
<field name="warn_manager">1</field>
|
||||
<field name="name">OpenERP Integration</field>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<form string="Project">
|
||||
<group colspan="6" col="6">
|
||||
<field name="name" string="Project Name" select="1"/>
|
||||
<field name="parent_id" string="Parent Project"/>
|
||||
<field name="parent_id" domain="[('id','!=',active_id)]"/>
|
||||
<field name="user_id" string="Project Manager" select="1" attrs="{'readonly':[('state','in',['close', 'cancelled'])]}"/>
|
||||
<field name="date_start" string="Start Date" attrs="{'readonly':[('state','in',['close', 'cancelled'])]}"/>
|
||||
<field name="date" string="End Date" attrs="{'readonly':[('state','in',['close', 'cancelled'])]}"/>
|
||||
|
@ -211,7 +211,7 @@
|
|||
<form string="Task edition">
|
||||
<group colspan="6" col="6">
|
||||
<field name="name" select="1"/>
|
||||
<field name="project_id" select="1" domain="[('user_id','=',uid)]"/>
|
||||
<field name="project_id" select="1" on_change="onchange_project(project_id)" domain="[('user_id','=',uid)]"/>
|
||||
<field name="total_hours" widget="float_time"/>
|
||||
<field name="date_deadline" attrs="{'readonly':[('state','in',['done', 'cancelled'])]}"/>
|
||||
<field name="user_id" select="1" attrs="{'readonly':[('state','in',['done', 'cancelled'])]}"/>
|
||||
|
|
|
@ -3,7 +3,17 @@
|
|||
<data>
|
||||
|
||||
<!-- Partners inherited form -->
|
||||
|
||||
<record id="base.view_crm_partner_info_History" model="ir.ui.view">
|
||||
<field name="name">res.partner.crm.history.inherit1</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form/notebook/page[@string='History']" position="attributes">
|
||||
<attribute name="invisible">False</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_task_partner_info_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.task.info.inherit</field>
|
||||
<field name="model">res.partner</field>
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="view_project_caldav_task_form2" model="ir.ui.view">
|
||||
<field name="name">project.task.caldav.form2</field>
|
||||
<field name="model">project.task</field>
|
||||
|
@ -65,64 +65,70 @@
|
|||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="progress" position="after">
|
||||
<group colspan="2" col="4" >
|
||||
<field name="rrule_type" string="Recurrency" colspan="1" attrs="{'readonly':[('recurrent_uid','!=',False)]}"/>
|
||||
<button string="Edit All"
|
||||
help="Edit all Ourrences of recurrent Task"
|
||||
attrs="{'invisible':[('rrule_type','in', ('none', False))]}"
|
||||
name="open_task" icon="gtk-edit"
|
||||
type="object" />
|
||||
<button string="Exclude range" groups="base.group_extended"
|
||||
help="Add Exception Rule"
|
||||
name="%(base_calendar.action_base_calendar_set_exrule)d" icon="gtk-zoom-out" type="action"
|
||||
context="{'model' : 'project.task'}"
|
||||
attrs="{'invisible':[('rrule_type','in', ('none', False))]}"/>
|
||||
</group>
|
||||
<newline/>
|
||||
<group col="4" colspan="6" name="rrule" attrs="{'invisible': [('rrule_type','!=','custom')]}">
|
||||
<separator string="Custom Recurrency Rule" colspan="8"/>
|
||||
<group col="8" colspan="4">
|
||||
<field name="interval" />
|
||||
<field name="freq" />
|
||||
<field name="count" />
|
||||
<field name="end_date" />
|
||||
</group>
|
||||
<group col="14" colspan="4" name="Select weekdays"
|
||||
attrs="{'invisible': [('freq','=','weekly')]}">
|
||||
<field name="mo" colspan="1" />
|
||||
<field name="tu" colspan="1" />
|
||||
<field name="we" colspan="1" />
|
||||
<field name="th" colspan="1" />
|
||||
<field name="fr" colspan="1" />
|
||||
<field name="sa" colspan="1" />
|
||||
<field name="su" colspan="1" />
|
||||
<newline />
|
||||
</group>
|
||||
<group col="10" colspan="4" attrs="{'invisible': [('freq','!=','monthly'), ('freq','!=','yearly')]}">
|
||||
<group col="2" colspan="1">
|
||||
<field name="select1" />
|
||||
</group>
|
||||
<group col="2" colspan="1" attrs="{'invisible' : [('select1','=','day')]}">
|
||||
<field name="day" attrs="{'required' : [('select1','=','date')]}" />
|
||||
</group>
|
||||
<group col="3" colspan="1" attrs="{'invisible' : [('select1','=','date')]}">
|
||||
<field name="byday" string="The" attrs="{'required' : [('select1','=','day')]}" />
|
||||
<field name="week_list" nolabel="1" attrs="{'required' : [('select1','=','day')]}" />
|
||||
</group>
|
||||
<group col="1" colspan="1" attrs="{'invisible' : [('freq','!=','yearly')]}">
|
||||
<field name="month_list" string="of" colspan="1" attrs="{'required' : [('freq','=','yearly')]}" />
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<field name="recurrency"/>
|
||||
<field name="edit_all" attrs="{'invisible':[('recurrency','=', False)]}"
|
||||
on_change="onchange_edit_all(rrule_type,edit_all)"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_project_caldav_task_form3" model="ir.ui.view">
|
||||
<record id="view_project_caldav_task_form3" model="ir.ui.view">
|
||||
<field name="name">project.task.caldav.form3</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2" />
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<notebook colspan="4" position="inside">
|
||||
<page string="Recurrency Option" attrs="{'invisible':[('recurrency','=',False)]}">
|
||||
<group colspan="2" col="4" >
|
||||
<field name="rrule_type" string="Recurrency" colspan="1" attrs="{'readonly':[('recurrent_uid','!=',False)]}"/>
|
||||
</group>
|
||||
<newline/>
|
||||
<group col="4" colspan="6" name="rrule">
|
||||
<separator string="Recurrency Rule" colspan="8"/>
|
||||
<group col="6" colspan="4">
|
||||
<field name="interval" string="Repeat Times" attrs="{'readonly': [('end_date','!=',False)]}"/>
|
||||
<field name="count" attrs="{'readonly': [('end_date','!=',False)]}"/>
|
||||
<field name="end_date" attrs="{'invisible': [('readonly','!=',False)]}"/>
|
||||
</group>
|
||||
<group col="14" colspan="4" name="Select weekdays"
|
||||
attrs="{'invisible' :[('rrule_type','not in', ['weekly','daily_working'])]}">
|
||||
<field name="mo" colspan="1" />
|
||||
<field name="tu" colspan="1" />
|
||||
<field name="we" colspan="1" />
|
||||
<field name="th" colspan="1" />
|
||||
<newline/>
|
||||
<field name="fr" colspan="1" />
|
||||
<field name="sa" colspan="1" attrs="{'invisible': [('rrule_type','=','daily_working')]}"/>
|
||||
<field name="su" colspan="1" attrs="{'invisible': [('rrule_type','=','daily_working')]}"/>
|
||||
<newline />
|
||||
</group>
|
||||
<group col="10" colspan="4" attrs="{'invisible': [('rrule_type','!=','monthly'), ('rrule_type','!=','yearly')]}">
|
||||
<group col="2" colspan="1">
|
||||
<field name="select1" />
|
||||
</group>
|
||||
<group col="2" colspan="1" attrs="{'invisible' : [('select1','=','day')]}">
|
||||
<field name="day" attrs="{'required' : [('select1','=','date')]}" />
|
||||
</group>
|
||||
<group col="3" colspan="1" attrs="{'invisible' : [('select1','=','date')]}">
|
||||
<field name="byday" string="The" attrs="{'required' : [('select1','=','day')]}" />
|
||||
<field name="week_list" nolabel="1" attrs="{'required' : [('select1','=','day')]}" />
|
||||
</group>
|
||||
<group col="1" colspan="1" attrs="{'invisible' : [('rrule_type','!=','yearly')]}">
|
||||
<field name="month_list" string="of" colspan="1" attrs="{'required' : [('rrule_type','=','yearly')]}" />
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_project_caldav_task_form4" model="ir.ui.view">
|
||||
<field name="name">project.task.caldav.form4</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2" />
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="after">
|
||||
<field name="vtimezone" colspan="2" />
|
||||
|
|
|
@ -151,6 +151,7 @@
|
|||
<page string="Task Detail">
|
||||
<field colspan="4" name="task_ids" context="{'default_project_id' :project_id}" nolabel="1">
|
||||
<tree editable="bottom" string="Project's Tasks">
|
||||
<field name="sequence"/>
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<field name="planned_hours" widget="float_time"/>
|
||||
|
@ -197,9 +198,9 @@
|
|||
<field name="sequence"/>
|
||||
</group>
|
||||
<separator colspan="4" string="Previous Phases"/>
|
||||
<field colspan="4" name="previous_phase_ids" nolabel="1"/>
|
||||
<field colspan="4" name="previous_phase_ids" nolabel="1" domain="[('id','!=',active_id)]"/>
|
||||
<separator colspan="4" string="Next Phases"/>
|
||||
<field colspan="4" name="next_phase_ids" nolabel="1"/>
|
||||
<field colspan="4" name="next_phase_ids" nolabel="1" domain="[('id','!=',active_id)]"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<newline/>
|
||||
|
|
|
@ -220,4 +220,15 @@ class task(osv.osv):
|
|||
return super(task,self).write(cr, uid, ids, vals, context)
|
||||
|
||||
task()
|
||||
|
||||
class res_partner(osv.osv):
|
||||
_inherit = 'res.partner'
|
||||
def unlink(self, cursor, user, ids, context=None):
|
||||
parnter_id=self.pool.get('project.project').search(cursor, user, [('partner_id', 'in', ids)])
|
||||
if parnter_id:
|
||||
raise osv.except_osv(_('Invalid action !'), _('Cannot delete Partner which is Assigned to project !'))
|
||||
return super(res_partner,self).unlink(cursor, user, ids,
|
||||
context=context)
|
||||
res_partner()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -123,6 +123,11 @@
|
|||
<field name="search_view_id" ref="view_account_analytic_line_filter_inherit_buttons"/>
|
||||
<field name="help">This lists show you every task work you can invoice to the customer. Select the lines in order to generate the invoices automatically.</field>
|
||||
</record>
|
||||
<menuitem action="hr_timesheet.action_hr_timesheet_sign_in"
|
||||
id="menu_hr_timesheet_sign_in"
|
||||
groups="base.group_extended"
|
||||
parent="hr_attendance.menu_hr_attendance"
|
||||
sequence="5" />
|
||||
|
||||
<menuitem id="menu_project_billing" name="Invoicing"
|
||||
parent="base.menu_main_pm" sequence="5"/>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
properly.
|
||||
The Plugin allows you archive email and its attachments to the selected
|
||||
OpenERP objects. You can select a partner, a task, a project, an analytical
|
||||
account,or any other object and attach selected mail as .eml file in
|
||||
account,or any other object and attach selected mail as eml file in
|
||||
attachment of selected record. You can create Documents for crm Lead,
|
||||
HR Applicant and project issue from selected mails.
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ class thunderbird_installer(osv.osv_memory):
|
|||
_columns = {
|
||||
'name':fields.char('File name', size=34),
|
||||
'pdf_name':fields.char('File name', size=64),
|
||||
'thunderbird':fields.boolean('Thunderbird Plug-in', help="Allows you to select an object that you’d like to add to your email and its attachments."),
|
||||
'thunderbird':fields.boolean('Thunderbird Plug-in', help="Allows you to select an object that you would like to add to your email and its attachments."),
|
||||
'plugin_file':fields.binary('Thunderbird Plug-in', readonly=True, help="Thunderbird plug-in file. Save as this file and install this plug-in in thunderbird."),
|
||||
'pdf_file':fields.char('Installation Manual', size="264", help="The documentation file :- how to install Thunderbird Plug-in.", readonly=True),
|
||||
'description':fields.text('Description', readonly=True)
|
||||
|
|
Binary file not shown.
Binary file not shown.
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/config.js
Executable file → Normal file
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/config.js
Executable file → Normal file
72
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/config.xul
Executable file → Normal file
72
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/config.xul
Executable file → Normal file
|
@ -4,7 +4,7 @@
|
|||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
title="&title.label;" onload="myPrefObserver.register();myPrefObserver.webregister();" unload="myPrefObserver.unregister()" height="410" width="900">
|
||||
title="&title.label;" onload="myPrefObserver.register();myPrefObserver.webregister();" unload="myPrefObserver.unregister()" height="430" width="900">
|
||||
<script type="text/javascript" src="chrome://openerp_plugin/content/tiny_xmlrpc.js"></script>
|
||||
<script type="text/javascript" src="chrome://openerp_plugin/content/config.js"></script>
|
||||
<script type="text/javascript" src="chrome://openerp_plugin/content/loaddata.js"></script>
|
||||
|
@ -16,20 +16,20 @@
|
|||
<tab label="About"/>
|
||||
</tabs>
|
||||
<tabpanels>
|
||||
<tabpanel id="configtab">
|
||||
<vbox style="border:1px solid black">
|
||||
<groupbox id="gpConnection" align ="center" >
|
||||
<separator class="groove-thin" orient="horizontal" width="94"/>
|
||||
<caption label="&gpConnection.label;"/>
|
||||
<tabpanel id="configtab">
|
||||
<vbox style="border:1px solid black">
|
||||
<groupbox id="gpConnection" align ="center" >
|
||||
<separator class="groove-thin" orient="horizontal" width="94"/>
|
||||
<caption label="&gpConnection.label;"/>
|
||||
<hbox>
|
||||
<label align="right" id="url" value="&txturl.label;" width="80" />
|
||||
<textbox id="txturl" width="200" readonly="true" />
|
||||
<button label="&getdblist.label;" oncommand="openConfigChange();" image="&imagesearch.value;" width="90"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
<hbox align="center" id="database_option">
|
||||
</hbox>
|
||||
<hbox id="first">
|
||||
<label align="right" id="lbldb_list1" control="DBlist" value="&database.label;" width="80"/>
|
||||
<label align="right" id="lbldb_list1" control="DBlist" value="&database.label;" width="80"/>
|
||||
<textbox id="DBlist_text" width="300"/>
|
||||
</hbox>
|
||||
|
||||
|
@ -43,33 +43,33 @@
|
|||
</hbox>
|
||||
<hbox >
|
||||
<spacer width="290"/>
|
||||
<button align="center" id="btconnection" label="&connection.label;" oncommand="testConnection();" image="&imageok.value;" width="100"/>
|
||||
</hbox>
|
||||
<button align="center" id="btconnection" label="&connection.label;" oncommand="testConnection();" image="&imageok.value;" width="100"/>
|
||||
</hbox>
|
||||
<separator class="groove-thin" orient="horizontal" width="94"/>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
|
||||
<separator class="groove-thin" orient="horizontal" width="10"/>
|
||||
<vbox style="border:1px solid black" width="94">
|
||||
<groupbox id="webgroup" >
|
||||
<vbox>
|
||||
<caption label="&webConnection.label;"/>
|
||||
</vbox>
|
||||
<separator class="groove-thin" orient="horizontal" width="10"/>
|
||||
<hbox>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
|
||||
<separator class="groove-thin" orient="horizontal" width="10"/>
|
||||
<vbox style="border:1px solid black" width="94">
|
||||
<groupbox id="webgroup" >
|
||||
<vbox>
|
||||
<caption label="&webConnection.label;"/>
|
||||
</vbox>
|
||||
<separator class="groove-thin" orient="horizontal" width="10"/>
|
||||
<hbox>
|
||||
<label align="right" id="url" value="&txtweburl.label;" width="80" />
|
||||
<textbox id="txtweburl" width="200" readonly="true"/>
|
||||
</hbox>
|
||||
|
||||
<textbox id="txtweburl" width="200" readonly="true"/>
|
||||
</hbox>
|
||||
|
||||
<hbox >
|
||||
<spacer width="113"/>
|
||||
<button align="center" id="websetconnection" label="&setconnection.label;" oncommand="openConfigChangeweb();" image="&imagesearch.value;"/>
|
||||
<button align="center" id="webopenconnection" label="&openconnection.label;" oncommand="testConnection_web();" image="&imageok.value;"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
|
||||
<button align="center" id="websetconnection" label="&setconnection.label;" oncommand="openConfigChangeweb();" image="&imagesearch.value;"/>
|
||||
<button align="center" id="webopenconnection" label="&openconnection.label;" oncommand="testConnection_web();" image="&imageok.value;"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel id="objecttab">
|
||||
<groupbox id="gpObject" width="700" >
|
||||
<caption label="&listDocument.header;"/>
|
||||
|
@ -88,7 +88,7 @@
|
|||
<button id="btobjectdelete" label="&documentdelete.label;" oncommand="deleteDocument();" image="&imagecancel.value;"/>
|
||||
</hbox>
|
||||
|
||||
<listbox id="listObjectListBox" flex="1" rows="10">
|
||||
<listbox id="listObjectListBox" flex="1" rows="12">
|
||||
<listhead>
|
||||
<listheader label="Title"/>
|
||||
<listheader label="&listDocumentListBox.header;"/>
|
||||
|
@ -110,20 +110,24 @@
|
|||
<tabpanel id="abouttab">
|
||||
<groupbox id="gpAbout" width="770" align="center">
|
||||
<caption label="&gpAbout.label;"/>
|
||||
<image src="chrome://openerp_plugin/skin/developped_by.png" sizemode="stretch" align="center"/>
|
||||
<description>&openerp.paresent;</description>
|
||||
<image src="chrome://openerp_plugin/skin/logo.png" sizemode="stretch" align="center"/>
|
||||
|
||||
<description> </description>
|
||||
<description> </description>
|
||||
<description>&develop.value;</description>
|
||||
<image src="chrome://openerp_plugin/skin/developped_by.png" sizemode="stretch" align="center"/>
|
||||
<description> </description>
|
||||
<description> </description>
|
||||
<description>&information.value;</description>
|
||||
<description><html:a href="&openerp.value;">&openerp.value;</html:a></description>
|
||||
|
||||
<description><html:a href="&axelor.value;">&axelor.value;</html:a></description>
|
||||
<description><html:a href="&tinyerp.value;">&tinyerp.value;</html:a></description>
|
||||
<description> </description>
|
||||
<description> </description>
|
||||
<description>Copyright © 2006-TODAY OpenERP SA All Rights Reserved.</description>
|
||||
<description>OpenERP is a trademark of the OpenERP SA Company. OpenERP Web is jointly developed by OpenERP SA and Axelor.</description>
|
||||
<description>OpenERP is a trademark of the OpenERP SA Company. OpenERP Web is jointly developed by OpenERP SA & Axelor.</description>
|
||||
<description>Licenced under the terms of <html:a href="https://tiny.odoo.com/LICENSE.txt"> OpenERP Public License (OEPL) v1.1 (https://tiny.odoo.com/LICENSE.txt)</html:a> </description>
|
||||
</groupbox>
|
||||
</tabpanel>
|
||||
|
|
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/loaddata.js
Executable file → Normal file
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/loaddata.js
Executable file → Normal file
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/overlay.js
Executable file → Normal file
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/overlay.js
Executable file → Normal file
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/overlay.xul
Executable file → Normal file
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/overlay.xul
Executable file → Normal file
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/tiny_xmlrpc.js
Executable file → Normal file
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/content/tiny_xmlrpc.js
Executable file → Normal file
16
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/locale/en-US/config.dtd
Executable file → Normal file
16
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/locale/en-US/config.dtd
Executable file → Normal file
|
@ -1,13 +1,13 @@
|
|||
<!ENTITY title.label "OpenERP Configuration">
|
||||
<!ENTITY separate.label "Configuration Login for OpenERP!">
|
||||
<!ENTITY close.label "Close">
|
||||
<!ENTITY database.label "Database:">
|
||||
<!ENTITY database.label "Database:">
|
||||
<!ENTITY ok.label "OK">
|
||||
<!ENTITY connection.label "Connect">
|
||||
<!ENTITY setconnection.label "Change">
|
||||
<!ENTITY connection.label "Connect">
|
||||
<!ENTITY setconnection.label "Change">
|
||||
<!ENTITY openconnection.label "Open">
|
||||
<!ENTITY txturl.label "Server: ">
|
||||
<!ENTITY txtweburl.label "Server: ">
|
||||
<!ENTITY txturl.label "Server: ">
|
||||
<!ENTITY txtweburl.label "Server: ">
|
||||
<!ENTITY txtwebport.label "Port: ">
|
||||
<!ENTITY image.label "Image : ">
|
||||
<!ENTITY obj.label "Document : ">
|
||||
|
@ -15,7 +15,7 @@
|
|||
<!ENTITY password.label "Password: ">
|
||||
<!ENTITY setdb.label "Move >">
|
||||
<!ENTITY getdblist.label "Change">
|
||||
<!ENTITY gpConnection.label "Connection Parameters">
|
||||
<!ENTITY gpConnection.label "Connection Parameters">
|
||||
<!ENTITY webConnection.label "Webserver Parameters">
|
||||
<!ENTITY listDBListBox.header "Available DBs">
|
||||
<!ENTITY listDocument.header "Document">
|
||||
|
@ -35,13 +35,15 @@ Licenced under the terms of OpenERP Public License (OEPL) v1.1 ">
|
|||
<!ENTITY axelor.value "http://www.axelor.com">
|
||||
<!ENTITY tinyerp.value "http://tiny.be/">
|
||||
|
||||
<!ENTITY openerp.paresent "This Thunderbird Plugin for OpenERP presently (2010-today) developed by OpenERP SA.">
|
||||
|
||||
<!ENTITY openerp_s.value "OpenERP">
|
||||
<!ENTITY axelor_s.value "The Axelor Company">
|
||||
<!ENTITY tinyerp_s.value "The Tiny Company">
|
||||
|
||||
<!ENTITY imageicon.value "chrome://openerp_plugin/skin/NEWT1.png">
|
||||
<!ENTITY gpAbout.label "About OpenERP Thunderbird Plugin">
|
||||
<!ENTITY develop.value "This Thunderbird Plugin for OpenERP has been developed by OpenERP SA & Axelor">
|
||||
<!ENTITY develop.value "Based on original work, copyright 2003-2009, by Tiny & Axelor ">
|
||||
<!ENTITY information.value "For more information, please visit our website">
|
||||
<!ENTITY contact.label "Contact Us">
|
||||
<!ENTITY contact.value "info@axelor.com">
|
||||
|
|
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/locale/en-US/overlay.dtd
Executable file → Normal file
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/locale/en-US/overlay.dtd
Executable file → Normal file
Binary file not shown.
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/skin/overlay.css
Executable file → Normal file
0
addons/thunderbird/plugin/openerp_plugin/chrome/openerp_plugin/skin/overlay.css
Executable file → Normal file
Loading…
Reference in New Issue