commit
b0a8f3b34d
|
@ -20,9 +20,14 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import osv, fields
|
||||
import time, pooler, copy
|
||||
|
||||
import ir
|
||||
from osv import fields,osv
|
||||
import netsvc
|
||||
import pooler
|
||||
import string
|
||||
import time,copy
|
||||
|
||||
class audittrail_rule(osv.osv):
|
||||
_name = 'audittrail.rule'
|
||||
_columns = {
|
||||
|
@ -49,13 +54,6 @@ class audittrail_rule(osv.osv):
|
|||
]
|
||||
__functions = {}
|
||||
|
||||
def __init__(self,pool,cr=None):
|
||||
for obj_name in pool.obj_list():
|
||||
obj=pool.get(obj_name)
|
||||
for field in ('read','write','create','unlink'):
|
||||
setattr(obj, field, self.logging_fct(getattr(obj,field), obj))
|
||||
super(audittrail_rule, self).__init__(pool,cr)
|
||||
|
||||
def subscribe(self, cr, uid, ids, *args):
|
||||
for thisrule in self.browse(cr, uid, ids):
|
||||
obj = self.pool.get(thisrule.object_id.model)
|
||||
|
@ -79,173 +77,6 @@ class audittrail_rule(osv.osv):
|
|||
return True
|
||||
|
||||
|
||||
def logging_fct(self, fct_src, obj):
|
||||
object_name=obj._name
|
||||
object=None
|
||||
logged_uids = []
|
||||
def get_value_text(cr, uid, field_name,values,object, context={}):
|
||||
f_id= self.pool.get('ir.model.fields').search(cr, uid,[('name','=',field_name),('model_id','=',object.id)])
|
||||
if f_id:
|
||||
field=self.pool.get('ir.model.fields').read(cr, uid,f_id)[0]
|
||||
model=field['relation']
|
||||
|
||||
if field['ttype']=='many2one':
|
||||
if values:
|
||||
if type(values)==tuple:
|
||||
values=values[0]
|
||||
val=self.pool.get(model).read(cr,uid,[values],['name'])
|
||||
if len(val):
|
||||
return val[0]['name']
|
||||
|
||||
elif field['ttype'] == 'many2many':
|
||||
value=[]
|
||||
if values:
|
||||
for id in values:
|
||||
val=self.pool.get(model).read(cr,uid,[id],['name'])
|
||||
if len(val):
|
||||
value.append(val[0]['name'])
|
||||
return value
|
||||
|
||||
elif field['ttype'] == 'one2many':
|
||||
|
||||
if values:
|
||||
value=[]
|
||||
for id in values:
|
||||
val=self.pool.get(model).read(cr,uid,[id],['name'])
|
||||
if len(val):
|
||||
value.append(val[0]['name'])
|
||||
return value
|
||||
return values
|
||||
|
||||
def create_log_line(cr,uid,id,object,lines=[]):
|
||||
for line in lines:
|
||||
f_id= self.pool.get('ir.model.fields').search(cr, uid,[('name','=',line['name']),('model_id','=',object.id)])
|
||||
if len(f_id):
|
||||
fields=self.pool.get('ir.model.fields').read(cr, uid,f_id)
|
||||
old_value='old_value' in line and line['old_value'] or ''
|
||||
new_value='new_value' in line and line['new_value'] or ''
|
||||
old_value_text='old_value_text' in line and line['old_value_text'] or ''
|
||||
new_value_text='new_value_text' in line and line['new_value_text'] or ''
|
||||
|
||||
if fields[0]['ttype']== 'many2one':
|
||||
if type(old_value)==tuple:
|
||||
old_value=old_value[0]
|
||||
if type(new_value)==tuple:
|
||||
new_value=new_value[0]
|
||||
self.pool.get('audittrail.log.line').create(cr, uid, {"log_id": id, "field_id": f_id[0] ,"old_value":old_value ,"new_value":new_value,"old_value_text":old_value_text ,"new_value_text":new_value_text,"field_description":fields[0]['field_description']})
|
||||
return True
|
||||
|
||||
def my_fct( cr, uid, *args, **args2):
|
||||
obj_ids= self.pool.get('ir.model').search(cr, uid,[('model','=',object_name)])
|
||||
if not len(obj_ids):
|
||||
return fct_src(cr, uid, *args, **args2)
|
||||
object=self.pool.get('ir.model').browse(cr,uid,obj_ids)[0]
|
||||
rule_ids=self.search(cr, uid, [('object_id','=',obj_ids[0]),('state','=','subscribed')])
|
||||
if not len(rule_ids):
|
||||
return fct_src(cr, uid, *args, **args2)
|
||||
|
||||
field=fct_src.__name__
|
||||
for thisrule in self.browse(cr, uid, rule_ids):
|
||||
if not getattr(thisrule, 'log_'+field):
|
||||
return fct_src(cr, uid, *args, **args2)
|
||||
self.__functions.setdefault(thisrule.id, [])
|
||||
self.__functions[thisrule.id].append( (obj,field, getattr(obj,field)) )
|
||||
for user in thisrule.user_id:
|
||||
logged_uids.append(user.id)
|
||||
|
||||
if fct_src.__name__ in ('create'):
|
||||
res_id =fct_src( cr, uid, *args, **args2)
|
||||
new_value=self.pool.get(object.model).read(cr,uid,[res_id],args[0].keys())[0]
|
||||
if 'id' in new_value:
|
||||
del new_value['id']
|
||||
if not len(logged_uids) or uid in logged_uids:
|
||||
id=self.pool.get('audittrail.log').create(cr, uid, {"method": fct_src.__name__, "object_id": object.id, "user_id": uid, "res_id": res_id,"name": "%s %s %s" % (fct_src.__name__, object.id, time.strftime("%Y-%m-%d %H:%M:%S"))})
|
||||
lines=[]
|
||||
for field in new_value:
|
||||
if new_value[field]:
|
||||
line={
|
||||
'name':field,
|
||||
'new_value':new_value[field],
|
||||
'new_value_text':get_value_text(cr,uid,field,new_value[field],object)
|
||||
}
|
||||
lines.append(line)
|
||||
create_log_line(cr,uid,id,object,lines)
|
||||
return res_id
|
||||
|
||||
if fct_src.__name__ in ('write'):
|
||||
res_ids=args[0]
|
||||
for res_id in res_ids:
|
||||
old_values=self.pool.get(object.model).read(cr,uid,res_id,args[1].keys())
|
||||
old_values_text={}
|
||||
for field in args[1].keys():
|
||||
old_values_text[field]=get_value_text(cr,uid,field,old_values[field],object)
|
||||
res =fct_src( cr, uid, *args, **args2)
|
||||
if res:
|
||||
new_values=self.pool.get(object.model).read(cr,uid,res_ids,args[1].keys())[0]
|
||||
if not len(logged_uids) or uid in logged_uids:
|
||||
id=self.pool.get('audittrail.log').create(cr, uid, {"method": fct_src.__name__, "object_id": object.id, "user_id": uid, "res_id": res_id,"name": "%s %s %s" % (fct_src.__name__, object.id, time.strftime("%Y-%m-%d %H:%M:%S"))})
|
||||
lines=[]
|
||||
for field in args[1].keys():
|
||||
if args[1].keys():
|
||||
line={
|
||||
'name':field,
|
||||
'new_value':field in new_values and new_values[field] or '',
|
||||
'old_value':field in old_values and old_values[field] or '',
|
||||
'new_value_text':get_value_text(cr,uid,field,new_values[field],object),
|
||||
'old_value_text':old_values_text[field]
|
||||
}
|
||||
lines.append(line)
|
||||
create_log_line(cr,uid,id,object,lines)
|
||||
return res
|
||||
|
||||
if fct_src.__name__ in ('read'):
|
||||
res_ids=args[0]
|
||||
old_values={}
|
||||
res =fct_src( cr, uid,*args, **args2)
|
||||
if type(res)==list:
|
||||
|
||||
for v in res:
|
||||
old_values[v['id']]=v
|
||||
else:
|
||||
|
||||
old_values[res['id']]=res
|
||||
for res_id in old_values:
|
||||
if not len(logged_uids) or uid in logged_uids:
|
||||
id=self.pool.get('audittrail.log').create(cr, uid, {"method": fct_src.__name__, "object_id": object.id, "user_id": uid, "res_id": res_id,"name": "%s %s %s" % (fct_src.__name__, object.id, time.strftime("%Y-%m-%d %H:%M:%S"))})
|
||||
lines=[]
|
||||
for field in old_values[res_id]:
|
||||
if old_values[res_id][field]:
|
||||
line={
|
||||
'name':field,
|
||||
'old_value':old_values[res_id][field],
|
||||
'old_value_text':get_value_text(cr,uid,field,old_values[res_id][field],object)
|
||||
}
|
||||
lines.append(line)
|
||||
create_log_line(cr,uid,id,object,lines)
|
||||
return res
|
||||
if fct_src.__name__ in ('unlink'):
|
||||
res_ids=args[0]
|
||||
old_values={}
|
||||
for res_id in res_ids:
|
||||
old_values[res_id]=self.pool.get(object.model).read(cr,uid,res_id,[])
|
||||
|
||||
for res_id in res_ids:
|
||||
if not len(logged_uids) or uid in logged_uids:
|
||||
id=self.pool.get('audittrail.log').create(cr, uid, {"method": fct_src.__name__, "object_id": object.id, "user_id": uid, "res_id": res_id,"name": "%s %s %s" % (fct_src.__name__, object.id, time.strftime("%Y-%m-%d %H:%M:%S"))})
|
||||
lines=[]
|
||||
for field in old_values[res_id]:
|
||||
if old_values[res_id][field]:
|
||||
line={
|
||||
'name':field,
|
||||
'old_value':old_values[res_id][field],
|
||||
'old_value_text':get_value_text(cr,uid,field,old_values[res_id][field],object)
|
||||
}
|
||||
lines.append(line)
|
||||
create_log_line(cr,uid,id,object,lines)
|
||||
res =fct_src( cr, uid,*args, **args2)
|
||||
return res
|
||||
|
||||
return my_fct
|
||||
|
||||
def unsubscribe(self, cr, uid, ids, *args):
|
||||
for thisrule in self.browse(cr, uid, ids):
|
||||
|
@ -297,5 +128,214 @@ class audittrail_log_line(osv.osv):
|
|||
}
|
||||
|
||||
audittrail_log_line()
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
def get_value_text(cr, uid, field_name,values,object, context={}):
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
obj=pool.get(object.model)
|
||||
object_name=obj._name
|
||||
obj_ids= pool.get('ir.model').search(cr, uid,[('model','=',object_name)])
|
||||
model_object=pool.get('ir.model').browse(cr,uid,obj_ids)[0]
|
||||
f_id= pool.get('ir.model.fields').search(cr, uid,[('name','=',field_name),('model_id','=',object.id)])
|
||||
if f_id:
|
||||
field=pool.get('ir.model.fields').read(cr, uid,f_id)[0]
|
||||
model=field['relation']
|
||||
|
||||
if field['ttype']=='many2one':
|
||||
if values:
|
||||
if type(values)==tuple:
|
||||
values=values[0]
|
||||
val=pool.get(model).read(cr,uid,[values],['name'])
|
||||
if len(val):
|
||||
return val[0]['name']
|
||||
|
||||
elif field['ttype'] == 'many2many':
|
||||
value=[]
|
||||
if values:
|
||||
for id in values:
|
||||
val=pool.get(model).read(cr,uid,[id],['name'])
|
||||
if len(val):
|
||||
value.append(val[0]['name'])
|
||||
return value
|
||||
|
||||
elif field['ttype'] == 'one2many':
|
||||
|
||||
if values:
|
||||
value=[]
|
||||
for id in values:
|
||||
val=pool.get(model).read(cr,uid,[id],['name'])
|
||||
if len(val):
|
||||
value.append(val[0]['name'])
|
||||
return value
|
||||
return values
|
||||
|
||||
def create_log_line(cr,uid,id,object,lines=[]):
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
obj=pool.get(object.model)
|
||||
object_name=obj._name
|
||||
obj_ids= pool.get('ir.model').search(cr, uid,[('model','=',object_name)])
|
||||
model_object=pool.get('ir.model').browse(cr,uid,obj_ids)[0]
|
||||
for line in lines:
|
||||
f_id= pool.get('ir.model.fields').search(cr, uid,[('name','=',line['name']),('model_id','=',object.id)])
|
||||
if len(f_id):
|
||||
fields=pool.get('ir.model.fields').read(cr, uid,f_id)
|
||||
old_value='old_value' in line and line['old_value'] or ''
|
||||
new_value='new_value' in line and line['new_value'] or ''
|
||||
old_value_text='old_value_text' in line and line['old_value_text'] or ''
|
||||
new_value_text='new_value_text' in line and line['new_value_text'] or ''
|
||||
|
||||
if fields[0]['ttype']== 'many2one':
|
||||
if type(old_value)==tuple:
|
||||
old_value=old_value[0]
|
||||
if type(new_value)==tuple:
|
||||
new_value=new_value[0]
|
||||
log_line_id = pool.get('audittrail.log.line').create(cr, uid, {"log_id": id, "field_id": f_id[0] ,"old_value":old_value ,"new_value":new_value,"old_value_text":old_value_text ,"new_value_text":new_value_text,"field_description":fields[0]['field_description']})
|
||||
cr.commit()
|
||||
return True
|
||||
|
||||
def log_fct( db, uid, passwd, object, method, fct_src , *args ):
|
||||
logged_uids = []
|
||||
pool = pooler.get_pool(db)
|
||||
cr = pooler.get_db(db).cursor()
|
||||
obj=pool.get(object)
|
||||
object_name=obj._name
|
||||
obj_ids= pool.get('ir.model').search(cr, uid,[('model','=',object_name)])
|
||||
model_object=pool.get('ir.model').browse(cr,uid,obj_ids)[0]
|
||||
if method in ('create'):
|
||||
res_id = fct_src( db, uid, passwd, object, method, *args)
|
||||
cr.commit()
|
||||
new_value=pool.get(model_object.model).read(cr,uid,[res_id],args[0].keys())[0]
|
||||
if 'id' in new_value:
|
||||
del new_value['id']
|
||||
if not len(logged_uids) or uid in logged_uids:
|
||||
id=pool.get('audittrail.log').create(cr, uid, {"method": method , "object_id": model_object.id, "user_id": uid, "res_id": res_id,"name": "%s %s %s" % (method , model_object.id, time.strftime("%Y-%m-%d %H:%M:%S"))})
|
||||
lines=[]
|
||||
for field in new_value:
|
||||
if new_value[field]:
|
||||
line={
|
||||
'name':field,
|
||||
'new_value':new_value[field],
|
||||
'new_value_text':get_value_text(cr,uid,field,new_value[field],model_object)
|
||||
}
|
||||
lines.append(line)
|
||||
create_log_line(cr,uid,id,model_object,lines)
|
||||
cr.commit()
|
||||
return res_id
|
||||
|
||||
if method in ('write'):
|
||||
res_ids=args[0]
|
||||
for res_id in res_ids:
|
||||
old_values=pool.get(model_object.model).read(cr,uid,res_id,args[1].keys())
|
||||
old_values_text={}
|
||||
for field in args[1].keys():
|
||||
old_values_text[field]=get_value_text(cr,uid,field,old_values[field],model_object)
|
||||
res =fct_src( db, uid, passwd, object, method, *args)
|
||||
cr.commit()
|
||||
if res:
|
||||
new_values=pool.get(model_object.model).read(cr,uid,res_ids,args[1].keys())[0]
|
||||
if not len(logged_uids) or uid in logged_uids:
|
||||
id=pool.get('audittrail.log').create(cr, uid, {"method": method, "object_id": model_object.id, "user_id": uid, "res_id": res_id,"name": "%s %s %s" % (method , model_object.id, time.strftime("%Y-%m-%d %H:%M:%S"))})
|
||||
lines=[]
|
||||
for field in args[1].keys():
|
||||
if args[1].keys():
|
||||
line={
|
||||
'name':field,
|
||||
'new_value':field in new_values and new_values[field] or '',
|
||||
'old_value':field in old_values and old_values[field] or '',
|
||||
'new_value_text':get_value_text(cr,uid,field,new_values[field],model_object),
|
||||
'old_value_text':old_values_text[field]
|
||||
}
|
||||
lines.append(line)
|
||||
cr.commit()
|
||||
create_log_line(cr,uid,id,model_object,lines)
|
||||
return res
|
||||
|
||||
if method in ('read'):
|
||||
res_ids=args[0]
|
||||
old_values={}
|
||||
res =fct_src( db, uid, passwd, object, method, *args)
|
||||
if type(res)==list:
|
||||
|
||||
for v in res:
|
||||
old_values[v['id']]=v
|
||||
else:
|
||||
old_values[res['id']]=res
|
||||
for res_id in old_values:
|
||||
if not len(logged_uids) or uid in logged_uids:
|
||||
id=pool.get('audittrail.log').create(cr, uid, {"method": method , "object_id": model_object.id, "user_id": uid, "res_id": res_id,"name": "%s %s %s" % (method , model_object.id, time.strftime("%Y-%m-%d %H:%M:%S"))})
|
||||
lines=[]
|
||||
for field in old_values[res_id]:
|
||||
if old_values[res_id][field]:
|
||||
line={
|
||||
'name':field,
|
||||
'old_value':old_values[res_id][field],
|
||||
'old_value_text':get_value_text(cr,uid,field,old_values[res_id][field],model_object)
|
||||
}
|
||||
lines.append(line)
|
||||
cr.commit()
|
||||
create_log_line(cr,uid,id,model_object,lines)
|
||||
return res
|
||||
|
||||
if method in ('unlink'):
|
||||
res_ids=args[0]
|
||||
old_values={}
|
||||
for res_id in res_ids:
|
||||
old_values[res_id]=pool.get(model_object.model).read(cr,uid,res_id,[])
|
||||
|
||||
for res_id in res_ids:
|
||||
if not len(logged_uids) or uid in logged_uids:
|
||||
id=pool.get('audittrail.log').create(cr, uid, {"method": method , "object_id": model_object.id, "user_id": uid, "res_id": res_id,"name": "%s %s %s" % (method, model_object, time.strftime("%Y-%m-%d %H:%M:%S"))})
|
||||
lines=[]
|
||||
for field in old_values[res_id]:
|
||||
if old_values[res_id][field]:
|
||||
line={
|
||||
'name':field,
|
||||
'old_value':old_values[res_id][field],
|
||||
'old_value_text':get_value_text(cr,uid,field,old_values[res_id][field],model_object)
|
||||
}
|
||||
lines.append(line)
|
||||
cr.commit()
|
||||
create_log_line(cr,uid,id,model_object,lines)
|
||||
res =fct_src( db, uid, passwd, object, method, *args)
|
||||
return res
|
||||
|
||||
def tmp_fct(fct_src):
|
||||
def execute( db, uid, passwd, object, method, *args):
|
||||
pool = pooler.get_pool(db)
|
||||
cr = pooler.get_db(db).cursor()
|
||||
obj=pool.get(object)
|
||||
logged_uids = []
|
||||
object_name=obj._name
|
||||
obj_ids= pool.get('ir.model').search(cr, uid,[('model','=',object_name)])
|
||||
model_object=pool.get('ir.model').browse(cr,uid,obj_ids)[0]
|
||||
|
||||
def my_fct( db, uid, passwd, object, method, *args):
|
||||
field = method
|
||||
rule = False
|
||||
obj_ids= pool.get('ir.model').search(cr, uid,[('model','=',object_name)])
|
||||
for obj_name in pool.obj_list():
|
||||
if obj_name == 'audittrail.rule':
|
||||
rule = True
|
||||
if not rule:
|
||||
return fct_src( db, uid, passwd, object, method, *args)
|
||||
if not len(obj_ids):
|
||||
return fct_src( db, uid, passwd, object, method, *args)
|
||||
rule_ids=pool.get('audittrail.rule').search(cr, uid, [('object_id','=',obj_ids[0]),('state','=','subscribed')])
|
||||
if not len(rule_ids):
|
||||
return fct_src( db, uid, passwd, object, method, *args)
|
||||
|
||||
for thisrule in pool.get('audittrail.rule').browse(cr, uid, rule_ids):
|
||||
for user in thisrule.user_id:
|
||||
logged_uids.append(user.id)
|
||||
if field in ('read','write','create','unlink'):
|
||||
if getattr(thisrule, 'log_'+field):
|
||||
return log_fct(db, uid, passwd, object, method, fct_src , *args)
|
||||
return fct_src( db, uid, passwd, object, method, *args)
|
||||
cr.commit()
|
||||
return my_fct( db, uid, passwd, object, method, *args)
|
||||
cr.close()
|
||||
return execute
|
||||
|
||||
obj = netsvc._service['object']
|
||||
obj.execute = tmp_fct(obj.execute)
|
||||
obj.exportMethod(obj.execute)
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -32,7 +32,10 @@ class profile_association_config_install_modules_wizard(osv.osv_memory):
|
|||
'board_document':fields.boolean('Document Management', help= "The Document Management System of Open ERP allows you to store, browse, automatically index, search and preview all kind of documents (internal documents, printed reports, calendar system). It opens an FTP access for the users to easily browse association's document."),
|
||||
'segmentation':fields.boolean('Segmentation'),
|
||||
'crm_configuration' : fields.boolean('Partner Relation & Calendars'),
|
||||
'project_gtd':fields.boolean('Getting Things Done', help="GTD is a methodology to efficiently organise yourself and your tasks. This module fully integrates GTD principle with OpenERP's project management."),
|
||||
'project_gtd':fields.boolean('Getting Things Done',
|
||||
help="GTD is a methodology to efficiently organise yourself and your tasks. This module fully integrates GTD principle with OpenERP's project management."),
|
||||
'wiki': fields.boolean('Wiki',
|
||||
help="An integrated wiki content management system. This is really usefull to manage FAQ, quality manuals, etc.")
|
||||
}
|
||||
def action_cancel(self,cr,uid,ids,conect=None):
|
||||
return {
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
<form string="Install Extra Module">
|
||||
<separator string="Project Management" colspan="4"/>
|
||||
<field name="project"/>
|
||||
<field name="project_gtd"/>
|
||||
<field name="wiki"/>
|
||||
<separator string="Resources Management" colspan="4"/>
|
||||
<field name="hr_expense"/>
|
||||
<separator string="Relationship Management" colspan="4"/>
|
||||
|
|
|
@ -37,9 +37,14 @@ class profile_service_config_install_modules_wizard(osv.osv_memory):
|
|||
help= "The Document Management System of Open ERP allows you to store, browse, automatically index, search and preview all kind of documents (internal documents, printed reports, calendar system). It opens an FTP access for the users to easily browse association's document."),
|
||||
'project_gtd':fields.boolean('Getting Things Done', help="GTD is a methodology to efficiently organise yourself and your tasks. This module fully integrates GTD principle with OpenERP's project management."),
|
||||
'scrum':fields.boolean('Scrum Methodology', help="Scrum is an 'agile development methodology', mainly used in IT projects. It helps you to manage teams, long term roadmaps, sprints, and so on."),
|
||||
'base_contact':fields.boolean('Advanced Contacts Management', help="Allows you to manage partners (enterprises), addresses of partners and contacts of these partners (employee/people). Install this if you plan to manage your relationships with partners and contacts, with contacts having different jobs in different companies."),
|
||||
'base_contact':fields.boolean('Advanced Contacts Management',
|
||||
help="Allows you to manage partners (enterprises), addresses of partners " \
|
||||
"and contacts of these partners (employee/people). Install this if you plan to manage your relationships with partners and contacts, with contacts having different jobs in different companies."),
|
||||
'portal': fields.boolean('Portal',
|
||||
help="This module allows you to manage a Portal system.")
|
||||
help="This module allows you to manage a Portal system."),
|
||||
'wiki': fields.boolean('Wiki',
|
||||
help="An integrated wiki content management system. This is really "\
|
||||
"usefull to manage FAQ, quality manuals, etc.")
|
||||
}
|
||||
def action_cancel(self,cr,uid,ids,conect=None):
|
||||
return {
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
<field name="hr_timesheet_invoice"/>
|
||||
<field name="project_gtd"/>
|
||||
<field name="scrum"/>
|
||||
|
||||
<separator string="Resources Management" colspan="4"/>
|
||||
<field name="hr_holidays"/>
|
||||
<field name="hr_expense"/>
|
||||
|
@ -21,8 +20,9 @@
|
|||
<field name="crm_configuration"/>
|
||||
<field name="base_contact"/>
|
||||
<field name="board_document"/>
|
||||
<separator string="Portal Management" colspan="4"/>
|
||||
<separator string="Portal Management" colspan="4"/>
|
||||
<field name="portal"/>
|
||||
<field name="wiki"/>
|
||||
<separator string="" colspan="4"/>
|
||||
<label string="" colspan="2"/>
|
||||
<group col="4" colspan="2">
|
||||
|
|
Loading…
Reference in New Issue