summaryrefslogtreecommitdiff
path: root/bin/addons
diff options
context:
space:
mode:
authorpinky <>2006-12-07 13:41:40 +0000
committerpinky <>2006-12-07 13:41:40 +0000
commit004a0b996ff8f269451e07346f71a129a1f3fbaf (patch)
tree48f43c1fc2ca4c1f4d5648e7e73553d3f4ccae06 /bin/addons
New trunk
bzr revid: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
Diffstat (limited to 'bin/addons')
-rw-r--r--bin/addons/__init__.py249
-rw-r--r--bin/addons/base/__init__.py32
-rw-r--r--bin/addons/base/__terp__.py36
-rw-r--r--bin/addons/base/base.sql335
-rw-r--r--bin/addons/base/base_data.xml1054
-rw-r--r--bin/addons/base/base_demo.xml274
-rw-r--r--bin/addons/base/base_menu.xml20
-rw-r--r--bin/addons/base/base_update.xml169
-rw-r--r--bin/addons/base/ir/__init__.py41
-rw-r--r--bin/addons/base/ir/ir.xml654
-rw-r--r--bin/addons/base/ir/ir_actions.py141
-rw-r--r--bin/addons/base/ir/ir_attachment.py43
-rw-r--r--bin/addons/base/ir/ir_cron.py139
-rw-r--r--bin/addons/base/ir/ir_default.py43
-rw-r--r--bin/addons/base/ir/ir_exports.py50
-rw-r--r--bin/addons/base/ir/ir_fields_description.py32
-rw-r--r--bin/addons/base/ir/ir_model.py273
-rw-r--r--bin/addons/base/ir/ir_report_custom.py206
-rw-r--r--bin/addons/base/ir/ir_sequence.py93
-rw-r--r--bin/addons/base/ir/ir_translation.py88
-rw-r--r--bin/addons/base/ir/ir_ui_menu.py123
-rw-r--r--bin/addons/base/ir/ir_ui_view.py76
-rw-r--r--bin/addons/base/ir/ir_values.py153
-rw-r--r--bin/addons/base/ir/workflow/__init__.py2
-rw-r--r--bin/addons/base/ir/workflow/instance_print.py88
-rw-r--r--bin/addons/base/ir/workflow/print_instance.py172
-rwxr-xr-xbin/addons/base/ir/workflow/pydot/LICENSE16
-rwxr-xr-xbin/addons/base/ir/workflow/pydot/PKG-INFO33
-rwxr-xr-xbin/addons/base/ir/workflow/pydot/README29
-rwxr-xr-xbin/addons/base/ir/workflow/pydot/__init__.py1
-rwxr-xr-xbin/addons/base/ir/workflow/pydot/dot_parser.py352
-rwxr-xr-xbin/addons/base/ir/workflow/pydot/pydot.py1075
-rwxr-xr-xbin/addons/base/ir/workflow/pydot/setup.py23
-rw-r--r--bin/addons/base/ir/workflow/workflow.py129
-rw-r--r--bin/addons/base/ir/workflow/workflow_print.py68
-rw-r--r--bin/addons/base/ir/workflow/workflow_view.xml209
-rw-r--r--bin/addons/base/module/__init__.py31
-rw-r--r--bin/addons/base/module/module.py274
-rw-r--r--bin/addons/base/module/module.py.old228
-rw-r--r--bin/addons/base/module/module_data.xml12
-rw-r--r--bin/addons/base/module/module_report.xml13
-rw-r--r--bin/addons/base/module/module_view.xml198
-rw-r--r--bin/addons/base/module/module_wizard.xml76
-rw-r--r--bin/addons/base/module/report/__init__.py29
-rw-r--r--bin/addons/base/module/report/ir_module_reference.rml173
-rw-r--r--bin/addons/base/module/report/ir_module_reference.sxwbin0 -> 12192 bytes
-rw-r--r--bin/addons/base/module/report/ir_module_reference_print.py60
-rw-r--r--bin/addons/base/module/wizard/__init__.py35
-rw-r--r--bin/addons/base/module/wizard/add_new.py100
-rw-r--r--bin/addons/base/module/wizard/wizard_export_lang.py91
-rw-r--r--bin/addons/base/module/wizard/wizard_import_lang.py75
-rw-r--r--bin/addons/base/module/wizard/wizard_install_module.py67
-rw-r--r--bin/addons/base/module/wizard/wizard_module_info_get.py46
-rw-r--r--bin/addons/base/module/wizard/wizard_module_lang_install.py83
-rw-r--r--bin/addons/base/module/wizard/wizard_module_upgrade.py81
-rw-r--r--bin/addons/base/module/wizard/wizard_update_module.py61
-rw-r--r--bin/addons/base/report/custom.xsl246
-rw-r--r--bin/addons/base/report/custom_default.xsl36
-rw-r--r--bin/addons/base/report/custom_new.xsl127
-rw-r--r--bin/addons/base/report/custom_rml.xsl62
-rw-r--r--bin/addons/base/report/rml_template.xsl70
-rw-r--r--bin/addons/base/res/__init__.py38
-rw-r--r--bin/addons/base/res/ir_property.py77
-rw-r--r--bin/addons/base/res/ir_property_view.xml43
-rw-r--r--bin/addons/base/res/partner/__init__.py37
-rw-r--r--bin/addons/base/res/partner/crm.py105
-rw-r--r--bin/addons/base/res/partner/crm_demo.xml39
-rw-r--r--bin/addons/base/res/partner/crm_view.xml116
-rw-r--r--bin/addons/base/res/partner/partner.py372
-rw-r--r--bin/addons/base/res/partner/partner_data.xml5
-rw-r--r--bin/addons/base/res/partner/partner_demo.xml288
-rw-r--r--bin/addons/base/res/partner/partner_report.xml9
-rw-r--r--bin/addons/base/res/partner/partner_view.xml504
-rw-r--r--bin/addons/base/res/partner/partner_wizard.xml11
-rw-r--r--bin/addons/base/res/partner/report/__init__.py32
-rw-r--r--bin/addons/base/res/partner/report/business_card.xml15
-rw-r--r--bin/addons/base/res/partner/report/business_card.xsl79
-rw-r--r--bin/addons/base/res/partner/report/partner_address.xml18
-rw-r--r--bin/addons/base/res/partner/report/partner_address.xsl85
-rw-r--r--bin/addons/base/res/partner/wizard/__init__.py32
-rw-r--r--bin/addons/base/res/partner/wizard/wizard_clear_ids.py45
-rw-r--r--bin/addons/base/res/partner/wizard/wizard_ean_check.py101
-rw-r--r--bin/addons/base/res/partner/wizard/wizard_sms.py75
-rw-r--r--bin/addons/base/res/partner/wizard/wizard_spam.py80
-rw-r--r--bin/addons/base/res/res_company.py105
-rw-r--r--bin/addons/base/res/res_currency.py70
-rw-r--r--bin/addons/base/res/res_currency_view.xml52
-rw-r--r--bin/addons/base/res/res_request.py124
-rw-r--r--bin/addons/base/res/res_request_view.xml125
-rw-r--r--bin/addons/base/res/res_user.py122
-rwxr-xr-xbin/addons/gen_graph.sh3
-rw-r--r--bin/addons/module_graph.py12
92 files changed, 11614 insertions, 0 deletions
diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py
new file mode 100644
index 00000000000..cb57399806d
--- /dev/null
+++ b/bin/addons/__init__.py
@@ -0,0 +1,249 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be)
+#
+# $Id: __init__.py 1308 2005-09-08 18:02:01Z pinky $
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contact a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import os, sys, imp
+import itertools
+from sets import Set
+
+import osv
+import tools
+import pooler
+
+
+import netsvc
+from osv import fields
+
+logger = netsvc.Logger()
+
+opj = os.path.join
+ad = tools.config['addons_path']
+sys.path.insert(1,ad)
+
+class Graph(dict):
+
+ def addNode(self, name, deps):
+ max_depth, father = 0, None
+ for n in [Node(x, self) for x in deps]:
+ if n.depth >= max_depth:
+ father = n
+ max_depth = n.depth
+ if father:
+ father.addChild(name)
+ else:
+ Node(name, self)
+
+ def __iter__(self):
+ level = 0
+ done = Set(self.keys())
+ while done:
+ level_modules = [(name, module) for name, module in self.items() if module.depth==level]
+ for name, module in level_modules:
+ done.remove(name)
+ yield module
+ level += 1
+
+class Singleton(object):
+
+ def __new__(cls, name, graph):
+ if name in graph:
+ inst = graph[name]
+ else:
+ inst = object.__new__(cls)
+ inst.name = name
+ graph[name] = inst
+ return inst
+
+class Node(Singleton):
+
+ def __init__(self, name, graph):
+ self.graph = graph
+ if not hasattr(self, 'childs'):
+ self.childs = []
+ if not hasattr(self, 'depth'):
+ self.depth = 0
+
+ def addChild(self, name):
+ node = Node(name, self.graph)
+ node.depth = self.depth + 1
+ if node not in self.childs:
+ self.childs.append(node)
+ for attr in ('init', 'update', 'demo'):
+ if hasattr(self, attr):
+ setattr(node, attr, True)
+ self.childs.sort(lambda x,y: cmp(x.name, y.name))
+
+ def hasChild(self, name):
+ return Node(name, self.graph) in self.childs or \
+ bool([c for c in self.childs if c.hasChild(name)])
+
+ def __setattr__(self, name, value):
+ super(Singleton, self).__setattr__(name, value)
+ if name in ('init', 'update', 'demo'):
+ tools.config[name][self.name] = 1
+ for child in self.childs:
+ setattr(child, name, value)
+ if name == 'depth':
+ for child in self.childs:
+ setattr(child, name, value + 1)
+
+ def __iter__(self):
+ return itertools.chain(iter(self.childs), *map(iter, self.childs))
+
+ def __str__(self):
+ return self._pprint()
+
+ def _pprint(self, depth=0):
+ s = '%s\n' % self.name
+ for c in self.childs:
+ s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1))
+ return s
+
+def create_graph(module_list, force=[]):
+ graph = Graph()
+ packages = []
+
+ for module in module_list:
+ terp_file = opj(ad, module, '__terp__.py')
+ if os.path.isfile(terp_file):
+ info = eval(file(terp_file).read())
+ if info.get('installable', True):
+ packages.append((module, info.get('depends', []), info))
+
+ current,later = Set([p for p, dep, data in packages]), Set()
+ while packages and current > later:
+ package, deps, datas = packages[0]
+
+ # if all dependencies of 'package' are already in the graph, add 'package' in the graph
+ if reduce(lambda x,y: x and y in graph, deps, True):
+ later.clear()
+ current.remove(package)
+ graph.addNode(package, deps)
+ node = Node(package, graph)
+ node.datas = datas
+ for kind in ('init', 'demo', 'update'):
+ if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
+ setattr(node, kind, True)
+ else:
+ later.add(package)
+ packages.append((package, deps, datas))
+ packages = packages[1:]
+
+ for package in later:
+ logger.notifyChannel('init', netsvc.LOG_ERROR, 'addon:%s:Unmet dependency' % package)
+
+ return graph
+
+def init_module_objects(cr, module_name, obj_list):
+ pool = pooler.get_pool(cr.dbname)
+ logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:creating or updating database tables' % module_name)
+ for obj in obj_list:
+ #CHECKME: is this test useful? all objects are supposed to have an _auto_init method, right?
+ #if hasattr(obj, '_auto_init'):
+ if hasattr(obj, 'init'):
+ obj.init(cr)
+ obj._auto_init(cr)
+ cr.commit()
+
+def load_module_graph(cr, graph, status={}):
+ package_todo = []
+ statusi = 0
+ for package in graph:
+ status['progress'] = (float(statusi)+0.1)/len(graph)
+ m = package.name
+ logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s' % m)
+ sys.stdout.flush()
+ pool = pooler.get_pool(cr.dbname)
+ modules = pool.instanciate(m)
+ cr.execute('select state, demo from ir_module_module where name=%s', (m,))
+ (package_state, package_demo) = (cr.rowcount and cr.fetchone()) or ('uninstalled', False)
+ idref = {}
+ status['progress'] = (float(statusi)+0.4)/len(graph)
+ if hasattr(package, 'init') or hasattr(package, 'update') or package_state in ('to install', 'to upgrade'):
+ init_module_objects(cr, m, modules)
+ for kind in ('init', 'update'):
+ for filename in package.datas.get('%s_xml' % kind, []):
+ mode = 'update'
+ if hasattr(package, 'init') or package_state=='to install':
+ mode = 'init'
+ logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:loading %s' % (m, filename))
+ name, ext = os.path.splitext(filename)
+ if ext == '.csv':
+ tools.convert_csv_import(cr, m, filename, tools.file_open(opj(m, filename)).read(), idref, mode=mode)
+ elif ext == '.sql':
+ queries = tools.file_open(opj(m, filename)).read().split(';')
+ for query in queries:
+ new_query = ' '.join(query.split())
+ if new_query:
+ cr.execute(new_query)
+ cr.commit()
+ else:
+ tools.convert_xml_import(cr, m, tools.file_open(opj(m, filename)).read(), idref, mode=mode)
+ if hasattr(package, 'demo') or (package_demo and package_state != 'installed'):
+ status['progress'] = (float(statusi)+0.75)/len(graph)
+ for xml in package.datas.get('demo_xml', []):
+ name, ext = os.path.splitext(xml)
+ logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:loading %s' % (m, xml))
+ if ext == '.csv':
+ tools.convert_csv_import(cr, m, os.path.basename(xml), tools.file_open(opj(m, xml)).read(), idref)
+ else:
+ tools.convert_xml_import(cr, m, tools.file_open(opj(m, xml)).read(), idref)
+ package_todo.append(package.name)
+ cr.execute("update ir_module_module set state='installed', demo=%s where state in ('to upgrade', 'to install') and name=%s", (str(hasattr(package, 'demo')) or package_demo, package.name))
+ cr.commit()
+ statusi+=1
+ cr.commit()
+
+# pool = osv.osv.FakePool('base')
+# pool = pooler.get_pool(cr.dbname)
+# pool.get('ir.model.data')._process_end(cr, 1, package_todo)
+# cr.commit()
+
+def register_classes():
+ module_list = os.listdir(ad)
+ for package in create_graph(module_list):
+ m = package.name
+ logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:registering classes' % m)
+ sys.stdout.flush()
+ imp.load_module(m, *imp.find_module(m))
+
+def load_modules(db, force_demo=False, status={}, update_module=False):
+ force = []
+ if force_demo:
+ force.append('demo')
+ cr = db.cursor()
+ if update_module:
+ cr.execute("select name from ir_module_module where state in ('installed', 'to install', 'to upgrade')")
+ else:
+ cr.execute("select name from ir_module_module where state = 'installed'")
+ module_list = [name for (name,) in cr.fetchall()]
+ graph = create_graph(module_list, force)
+ load_module_graph(cr, graph, status)
+ cr.commit()
+ cr.close()
+
diff --git a/bin/addons/base/__init__.py b/bin/addons/base/__init__.py
new file mode 100644
index 00000000000..8d5f263d4a0
--- /dev/null
+++ b/bin/addons/base/__init__.py
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import ir
+import module
+import res
+
diff --git a/bin/addons/base/__terp__.py b/bin/addons/base/__terp__.py
new file mode 100644
index 00000000000..086b0c448da
--- /dev/null
+++ b/bin/addons/base/__terp__.py
@@ -0,0 +1,36 @@
+{
+ "name" : "Base",
+ "version" : "1.0",
+ "author" : "Tiny",
+ "website" : "http://tinyerp.com",
+ "category" : "Generic Modules/Base",
+ "description": "The kernel of Tiny ERP, needed for all installation.",
+ "depends" : [],
+ "init_xml" : [
+ "base_data.xml",
+ "base_menu.xml"
+ ],
+ "demo_xml" : [
+ "base_demo.xml",
+ "res/partner/partner_demo.xml",
+ "res/partner/crm_demo.xml",
+ ],
+ "update_xml" : [
+ "base_update.xml",
+ "ir/ir.xml",
+ "ir/workflow/workflow_view.xml",
+ "module/module_data.xml",
+ "module/module_wizard.xml",
+ "module/module_view.xml",
+ "module/module_report.xml",
+ "res/res_request_view.xml",
+ "res/partner/partner_report.xml",
+ "res/partner/partner_view.xml",
+ "res/partner/partner_wizard.xml",
+ "res/res_currency_view.xml",
+ "res/partner/crm_view.xml",
+ "res/partner/partner_data.xml",
+ "res/ir_property_view.xml",
+ ],
+ "installable": True
+}
diff --git a/bin/addons/base/base.sql b/bin/addons/base/base.sql
new file mode 100644
index 00000000000..7d980cd0c85
--- /dev/null
+++ b/bin/addons/base/base.sql
@@ -0,0 +1,335 @@
+-------------------------------------------------------------------------
+-- Pure SQL
+-------------------------------------------------------------------------
+
+CREATE TABLE perm (
+ id serial NOT NULL,
+ level smallint DEFAULT 4 NOT NULL,
+ uid int default null,
+ gid int default null,
+ primary key(id)
+);
+insert into perm (id,uid,gid) values (1,1,1);
+
+CREATE TABLE inherit (
+ obj_type varchar(128) not null,
+ obj_id int not null,
+ inst_type varchar(128) not null,
+ inst_id int not null
+);
+
+-------------------------------------------------------------------------
+-- IR dictionary
+-------------------------------------------------------------------------
+
+create table ir_values
+(
+ id serial,
+ perm_id int references perm on delete set null,
+ name varchar(128) not null,
+ key varchar(128) not null,
+ key2 varchar(128) not null,
+ model varchar(128) not null,
+ value text,
+ meta text default NULL,
+ res_id integer default null,
+ primary key (id)
+);
+
+-------------------------------------------------------------------------
+-- Modules Description
+-------------------------------------------------------------------------
+
+CREATE TABLE ir_model (
+ id serial,
+ model varchar(64) DEFAULT ''::varchar NOT NULL,
+ name varchar(64),
+ info text,
+ primary key(id)
+);
+
+CREATE TABLE ir_model_fields (
+ id serial,
+ model varchar(64) DEFAULT ''::varchar NOT NULL,
+ model_id int references ir_model,
+ name varchar(64) DEFAULT ''::varchar NOT NULL,
+ relation varchar(64),
+ field_description varchar(256),
+ ttype varchar(64),
+ group_name varchar(64),
+ view_load boolean,
+ relate boolean default False,
+ primary key(id)
+);
+
+
+-------------------------------------------------------------------------
+-- Actions
+-------------------------------------------------------------------------
+
+CREATE TABLE ir_actions (
+ id serial NOT NULL,
+ perm_id int references perm on delete set null,
+ name varchar(64) DEFAULT ''::varchar NOT NULL,
+ "type" varchar(64) DEFAULT 'window'::varchar NOT NULL,
+ usage varchar(32) DEFAULT null,
+ primary key(id)
+);
+
+CREATE TABLE ir_act_window (
+ view_id integer,
+ res_model varchar(64),
+ view_type varchar(16),
+ "domain" varchar(127),
+ primary key(id)
+)
+INHERITS (ir_actions);
+
+CREATE TABLE ir_act_report_xml (
+ model varchar(64) NOT NULL,
+ report_name varchar(64) NOT NULL,
+ report_xsl varchar(64),
+ report_xml varchar(64),
+ auto boolean default true
+)
+INHERITS (ir_actions);
+
+create table ir_act_report_custom (
+ report_id int
+-- report_id int references ir_report_custom
+)
+INHERITS (ir_actions);
+
+CREATE TABLE ir_act_group (
+ exec_type varchar(64) DEFAULT 'serial'::varchar NOT NULL
+)
+INHERITS (ir_actions);
+
+CREATE TABLE ir_act_group_link (
+ aid integer NOT NULL,
+ gid integer NOT NULL
+);
+
+CREATE TABLE ir_act_execute (
+ func_name varchar(64) NOT NULL,
+ func_arg varchar(64)
+)
+INHERITS (ir_actions);
+
+CREATE TABLE ir_act_wizard (
+ wiz_name varchar(64) NOT NULL
+)
+INHERITS (ir_actions);
+
+CREATE TABLE ir_ui_view (
+ id serial NOT NULL,
+ perm_id int references perm on delete set null,
+ name varchar(64) DEFAULT ''::varchar NOT NULL,
+ model varchar(64) DEFAULT ''::varchar NOT NULL,
+ "type" varchar(64) DEFAULT 'form'::varchar NOT NULL,
+ arch text NOT NULL,
+ field_parent varchar(64),
+ priority integer DEFAULT 5 NOT NULL,
+ primary key(id),
+ CONSTRAINT ir_ui_view_type CHECK (((("type")::text = ('form'::varchar)::text) OR (("type")::text = ('tree'::varchar)::text)))
+);
+
+CREATE TABLE ir_ui_menu (
+ id serial NOT NULL,
+ perm_id int references perm on delete set null,
+ parent_id int references ir_ui_menu on delete set null,
+ name varchar(64) DEFAULT ''::varchar NOT NULL,
+ icon varchar(64) DEFAULT ''::varchar,
+ primary key (id)
+);
+
+select setval('ir_ui_menu_id_seq', 2);
+
+---------------------------------
+-- Res users
+---------------------------------
+
+-- level:
+-- 0 RESTRICT TO USER
+-- 1 RESTRICT TO GROUP
+-- 2 PUBLIC
+
+CREATE TABLE res_users (
+ id serial NOT NULL,
+ perm_id int references perm on delete set null,
+ name varchar(64) not null,
+ active boolean default True,
+ login varchar(64) NOT NULL UNIQUE,
+ password varchar(32) default null,
+ perm_default int references perm on delete set null,
+-- action_id int references ir_act_window on delete set null,
+ action_id int,
+ primary key(id)
+);
+alter table res_users add constraint res_users_login_uniq unique (login);
+
+insert into res_users (id,login,password,name,action_id,perm_id,active) values (1,'root',NULL,'Administrator',NULL,1,False);
+select setval('res_users_id_seq', 2);
+
+CREATE TABLE res_groups (
+ id serial NOT NULL,
+ perm_id int references perm on delete set null,
+ name varchar(32) NOT NULL,
+ primary key(id)
+);
+
+create table res_roles (
+ id serial NOT NULL,
+ perm_id int references perm on delete set null,
+ parent_id int references res_roles on delete set null,
+ name varchar(32) NOT NULL,
+ primary key(id)
+);
+
+CREATE TABLE res_roles_users_rel (
+ uid integer NOT NULL references res_users on delete cascade,
+ rid integer NOT NULL references res_roles on delete cascade
+);
+
+CREATE TABLE res_groups_users_rel (
+ uid integer NOT NULL references res_users on delete cascade,
+ gid integer NOT NULL references res_groups on delete cascade
+);
+
+---------------------------------
+-- Workflows
+---------------------------------
+
+create table wkf
+(
+ id serial,
+ perm_id int references perm on delete set null,
+ name varchar(64),
+ osv varchar(64),
+ on_create bool default False,
+ primary key(id)
+);
+
+create table wkf_activity
+(
+ id serial,
+ perm_id int references perm on delete set null,
+ wkf_id int references wkf on delete cascade,
+ subflow_id int references wkf on delete set null,
+ split_mode varchar(3) default 'XOR',
+ join_mode varchar(3) default 'XOR',
+ kind varchar(16) not null default 'dummy',
+ name varchar(64),
+ signal_send varchar(32) default null,
+ flow_start boolean default False,
+ flow_stop boolean default False,
+ action varchar(64) default null,
+ primary key(id)
+);
+
+create table wkf_transition
+(
+ id serial,
+ perm_id int references perm on delete set null,
+ act_from int references wkf_activity on delete cascade,
+ act_to int references wkf_activity on delete cascade,
+ condition varchar(128) default NULL,
+
+ trigger_type varchar(128) default NULL,
+ trigger_expr_id varchar(128) default NULL,
+
+ signal varchar(64) default null,
+ role_id int references res_roles on delete set null,
+
+ primary key(id)
+);
+
+create table wkf_instance
+(
+ id serial,
+ wkf_id int references wkf on delete set null,
+ uid int default null,
+ res_id int not null,
+ res_type varchar(64) not null,
+ state varchar(32) not null default 'active',
+ primary key(id)
+);
+
+create table wkf_workitem
+(
+ id serial,
+ act_id int not null references wkf_activity on delete cascade,
+ inst_id int not null references wkf_instance on delete cascade,
+ subflow_id int references wkf_instance on delete cascade,
+ state varchar(64) default 'blocked',
+ primary key(id)
+);
+
+create table wkf_witm_trans
+(
+ trans_id int not null references wkf_transition on delete cascade,
+ inst_id int not null references wkf_instance on delete cascade
+);
+
+create table wkf_logs
+(
+ id serial,
+ res_type varchar(128) not null,
+ res_id int not null,
+ uid int references res_users on delete set null,
+ act_id int references wkf_activity on delete set null,
+ time time not null,
+ info varchar(128) default NULL,
+ primary key(id)
+);
+
+---------------------------------
+-- Modules
+---------------------------------
+
+CREATE TABLE ir_module_category (
+ id serial NOT NULL,
+ perm_id integer,
+ create_uid integer references res_users on delete set null,
+ create_date timestamp without time zone,
+ write_date timestamp without time zone,
+ write_uid integer references res_users on delete set null,
+ parent_id integer REFERENCES ir_module_category ON DELETE SET NULL,
+ name character varying(128) NOT NULL,
+ primary key(id)
+);
+
+
+CREATE TABLE ir_module_module (
+ id serial NOT NULL,
+ perm_id integer,
+ create_uid integer references res_users on delete set null,
+ create_date timestamp without time zone,
+ write_date timestamp without time zone,
+ write_uid integer references res_users on delete set null,
+ website character varying(256),
+ name character varying(128) NOT NULL,
+ author character varying(128),
+ url character varying(128),
+ state character varying(16),
+ latest_version character varying(64),
+ shortdesc character varying(256),
+ category_id integer REFERENCES ir_module_category ON DELETE SET NULL,
+ description text,
+ demo boolean default False,
+ primary key(id)
+);
+
+CREATE TABLE ir_module_module_dependency (
+ id serial NOT NULL,
+ perm_id integer,
+ create_uid integer references res_users on delete set null,
+ create_date timestamp without time zone,
+ write_date timestamp without time zone,
+ write_uid integer references res_users on delete set null,
+ name character varying(128),
+ version_pattern character varying(128) default NULL,
+ module_id integer REFERENCES ir_module_module ON DELETE cascade,
+ primary key(id)
+);
+
diff --git a/bin/addons/base/base_data.xml b/bin/addons/base/base_data.xml
new file mode 100644
index 00000000000..294a4b080a1
--- /dev/null
+++ b/bin/addons/base/base_data.xml
@@ -0,0 +1,1054 @@
+<?xml version="1.0"?>
+<terp>
+<data noupdate="1">
+ <record model="ir.ui.view" id="view_menu">
+ <field name="name">ir.ui.menu.tree</field>
+ <field name="model">ir.ui.menu</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Menu" toolbar="1">
+ <field name="name" icon="icon"/>
+ </tree>
+ </field>
+ <field name="field_parent">child_id</field>
+ </record>
+
+ <record model="ir.actions.act_window" id="action_menu_admin">
+ <field name="name">Menu</field>
+ <field name="usage">menu</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="view_id" ref="view_menu"/>
+ <field name="res_model">ir.ui.menu</field>
+ <field name="view_type">tree</field>
+ <field name="domain" eval="'[(\'parent_id\',\'=\',False)]'"/>
+ </record>
+
+ <record model="res.groups" id="group_admin">
+ <field name="name">admin</field>
+ </record>
+ <record model="res.groups" id="group_user">
+ <field name="name">user</field>
+ </record>
+
+ <record model="res.lang" id="lang_en">
+ <field name="code">en_EN</field>
+ <field name="name">English</field>
+ </record>
+
+ <record model="res.country" id="ad">
+ <field name="name">Andorra, Principality of</field>
+ <field name="code">ad</field>
+ </record>
+ <record model="res.country" id="ae">
+ <field name="name">United Arab Emirates</field>
+ <field name="code">ae</field>
+ </record>
+ <record model="res.country" id="af">
+ <field name="name">Afghanistan, Islamic State of</field>
+ <field name="code">af</field>
+ </record>
+ <record model="res.country" id="ag">
+ <field name="name">Antigua and Barbuda</field>
+ <field name="code">ag</field>
+ </record>
+ <record model="res.country" id="ai">
+ <field name="name">Anguilla</field>
+ <field name="code">ai</field>
+ </record>
+ <record model="res.country" id="al">
+ <field name="name">Albania</field>
+ <field name="code">al</field>
+ </record>
+ <record model="res.country" id="am">
+ <field name="name">Armenia</field>
+ <field name="code">am</field>
+ </record>
+ <record model="res.country" id="an">
+ <field name="name">Netherlands Antilles</field>
+ <field name="code">an</field>
+ </record>
+ <record model="res.country" id="ao">
+ <field name="name">Angola</field>
+ <field name="code">ao</field>
+ </record>
+ <record model="res.country" id="aq">
+ <field name="name">Antarctica</field>
+ <field name="code">aq</field>
+ </record>
+ <record model="res.country" id="ar">
+ <field name="name">Argentina</field>
+ <field name="code">ar</field>
+ </record>
+ <record model="res.country" id="as">
+ <field name="name">American Samoa</field>
+ <field name="code">as</field>
+ </record>
+ <record model="res.country" id="at">
+ <field name="name">Austria</field>
+ <field name="code">at</field>
+ </record>
+ <record model="res.country" id="au">
+ <field name="name">Australia</field>
+ <field name="code">au</field>
+ </record>
+ <record model="res.country" id="aw">
+ <field name="name">Aruba</field>
+ <field name="code">aw</field>
+ </record>
+ <record model="res.country" id="az">
+ <field name="name">Azerbaidjan</field>
+ <field name="code">az</field>
+ </record>
+ <record model="res.country" id="ba">
+ <field name="name">Bosnia-Herzegovina</field>
+ <field name="code">ba</field>
+ </record>
+ <record model="res.country" id="bb">
+ <field name="name">Barbados</field>
+ <field name="code">bb</field>
+ </record>
+ <record model="res.country" id="bd">
+ <field name="name">Bangladesh</field>
+ <field name="code">bd</field>
+ </record>
+ <record model="res.country" id="be">
+ <field name="name">Belgium</field>
+ <field name="code">be</field>
+ </record>
+ <record model="res.country" id="bf">
+ <field name="name">Burkina Faso</field>
+ <field name="code">bf</field>
+ </record>
+ <record model="res.country" id="bg">
+ <field name="name">Bulgaria</field>
+ <field name="code">bg</field>
+ </record>
+ <record model="res.country" id="bh">
+ <field name="name">Bahrain</field>
+ <field name="code">bh</field>
+ </record>
+ <record model="res.country" id="bi">
+ <field name="name">Burundi</field>
+ <field name="code">bi</field>
+ </record>
+ <record model="res.country" id="bj">
+ <field name="name">Benin</field>
+ <field name="code">bj</field>
+ </record>
+ <record model="res.country" id="bm">
+ <field name="name">Bermuda</field>
+ <field name="code">bm</field>
+ </record>
+ <record model="res.country" id="bn">
+ <field name="name">Brunei Darussalam</field>
+ <field name="code">bn</field>
+ </record>
+ <record model="res.country" id="bo">
+ <field name="name">Bolivia</field>
+ <field name="code">bo</field>
+ </record>
+ <record model="res.country" id="br">
+ <field name="name">Brazil</field>
+ <field name="code">br</field>
+ </record>
+ <record model="res.country" id="bs">
+ <field name="name">Bahamas</field>
+ <field name="code">bs</field>
+ </record>
+ <record model="res.country" id="bt">
+ <field name="name">Bhutan</field>
+ <field name="code">bt</field>
+ </record>
+ <record model="res.country" id="bv">
+ <field name="name">Bouvet Island</field>
+ <field name="code">bv</field>
+ </record>
+ <record model="res.country" id="bw">
+ <field name="name">Botswana</field>
+ <field name="code">bw</field>
+ </record>
+ <record model="res.country" id="by">
+ <field name="name">Belarus</field>
+ <field name="code">by</field>
+ </record>
+ <record model="res.country" id="bz">
+ <field name="name">Belize</field>
+ <field name="code">bz</field>
+ </record>
+ <record model="res.country" id="ca">
+ <field name="name">Canada</field>
+ <field name="code">ca</field>
+ </record>
+ <record model="res.country" id="cc">
+ <field name="name">Cocos (Keeling) Islands</field>
+ <field name="code">cc</field>
+ </record>
+ <record model="res.country" id="cf">
+ <field name="name">Central African Republic</field>
+ <field name="code">cf</field>
+ </record>
+ <record model="res.country" id="cd">
+ <field name="name">Congo, The Democratic Republic of the</field>
+ <field name="code">cd</field>
+ </record>
+ <record model="res.country" id="cg">
+ <field name="name">Congo</field>
+ <field name="code">cg</field>
+ </record>
+ <record model="res.country" id="ch">
+ <field name="name">Suisse</field>
+ <field name="code">ch</field>
+ </record>
+ <record model="res.country" id="ci">
+ <field name="name">Ivory Coast (Cote D'Ivoire)</field>
+ <field name="code">ci</field>
+ </record>
+ <record model="res.country" id="ck">
+ <field name="name">Cook Islands</field>
+ <field name="code">ck</field>
+ </record>
+ <record model="res.country" id="cl">
+ <field name="name">Chile</field>
+ <field name="code">cl</field>
+ </record>
+ <record model="res.country" id="cm">
+ <field name="name">Cameroon</field>
+ <field name="code">cm</field>
+ </record>
+ <record model="res.country" id="cn">
+ <field name="name">China</field>
+ <field name="code">cn</field>
+ </record>
+ <record model="res.country" id="co">
+ <field name="name">Colombia</field>
+ <field name="code">co</field>
+ </record>
+ <record model="res.country" id="cr">
+ <field name="name">Costa Rica</field>
+ <field name="code">cr</field>
+ </record>
+ <record model="res.country" id="cs">
+ <field name="name">Former Czechoslovakia</field>
+ <field name="code">cs</field>
+ </record>
+ <record model="res.country" id="cu">
+ <field name="name">Cuba</field>
+ <field name="code">cu</field>
+ </record>
+ <record model="res.country" id="cv">
+ <field name="name">Cape Verde</field>
+ <field name="code">cv</field>
+ </record>
+ <record model="res.country" id="cx">
+ <field name="name">Christmas Island</field>
+ <field name="code">cx</field>
+ </record>
+ <record model="res.country" id="cy">
+ <field name="name">Cyprus</field>
+ <field name="code">cy</field>
+ </record>
+ <record model="res.country" id="cz">
+ <field name="name">Czech Republic</field>
+ <field name="code">cz</field>
+ </record>
+ <record model="res.country" id="de">
+ <field name="name">Germany</field>
+ <field name="code">de</field>
+ </record>
+ <record model="res.country" id="dj">
+ <field name="name">Djibouti</field>
+ <field name="code">dj</field>
+ </record>
+ <record model="res.country" id="dk">
+ <field name="name">Denmark</field>
+ <field name="code">dk</field>
+ </record>
+ <record model="res.country" id="dm">
+ <field name="name">Dominica</field>
+ <field name="code">dm</field>
+ </record>
+ <record model="res.country" id="do">
+ <field name="name">Dominican Republic</field>
+ <field name="code">do</field>
+ </record>
+ <record model="res.country" id="dz">
+ <field name="name">Algeria</field>
+ <field name="code">dz</field>
+ </record>
+ <record model="res.country" id="ec">
+ <field name="name">Ecuador</field>
+ <field name="code">ec</field>
+ </record>
+ <record model="res.country" id="ee">
+ <field name="name">Estonia</field>
+ <field name="code">ee</field>
+ </record>
+ <record model="res.country" id="eg">
+ <field name="name">Egypt</field>
+ <field name="code">eg</field>
+ </record>
+ <record model="res.country" id="eh">
+ <field name="name">Western Sahara</field>
+ <field name="code">eh</field>
+ </record>
+ <record model="res.country" id="er">
+ <field name="name">Eritrea</field>
+ <field name="code">er</field>
+ </record>
+ <record model="res.country" id="es">
+ <field name="name">Spain</field>
+ <field name="code">es</field>
+ </record>
+ <record model="res.country" id="et">
+ <field name="name">Ethiopia</field>
+ <field name="code">et</field>
+ </record>
+ <record model="res.country" id="fi">
+ <field name="name">Finland</field>
+ <field name="code">fi</field>
+ </record>
+ <record model="res.country" id="fj">
+ <field name="name">Fiji</field>
+ <field name="code">fj</field>
+ </record>
+ <record model="res.country" id="fk">
+ <field name="name">Falkland Islands</field>
+ <field name="code">fk</field>
+ </record>
+ <record model="res.country" id="fm">
+ <field name="name">Micronesia</field>
+ <field name="code">fm</field>
+ </record>
+ <record model="res.country" id="fo">
+ <field name="name">Faroe Islands</field>
+ <field name="code">fo</field>
+ </record>
+ <record model="res.country" id="fr">
+ <field name="name">France</field>
+ <field name="code">fr</field>
+ </record>
+ <record model="res.country" id="ga">
+ <field name="name">Gabon</field>
+ <field name="code">ga</field>
+ </record>
+ <record model="res.country" id="gb">
+ <field name="name">Great Britain</field>
+ <field name="code">gb</field>
+ </record>
+ <record model="res.country" id="gd">
+ <field name="name">Grenada</field>
+ <field name="code">gd</field>
+ </record>
+ <record model="res.country" id="ge">
+ <field name="name">Georgia</field>
+ <field name="code">ge</field>
+ </record>
+ <record model="res.country" id="gf">
+ <field name="name">French Guyana</field>
+ <field name="code">gf</field>
+ </record>
+ <record model="res.country" id="gh">
+ <field name="name">Ghana</field>
+ <field name="code">gh</field>
+ </record>
+ <record model="res.country" id="gi">
+ <field name="name">Gibraltar</field>
+ <field name="code">gi</field>
+ </record>
+ <record model="res.country" id="gl">
+ <field name="name">Greenland</field>
+ <field name="code">gl</field>
+ </record>
+ <record model="res.country" id="gm">
+ <field name="name">Gambia</field>
+ <field name="code">gm</field>
+ </record>
+ <record model="res.country" id="gn">
+ <field name="name">Guinea</field>
+ <field name="code">gn</field>
+ </record>
+ <record model="res.country" id="gp">
+ <field name="name">Guadeloupe (French)</field>
+ <field name="code">gp</field>
+ </record>
+ <record model="res.country" id="gq">
+ <field name="name">Equatorial Guinea</field>
+ <field name="code">gq</field>
+ </record>
+ <record model="res.country" id="gr">
+ <field name="name">Greece</field>
+ <field name="code">gr</field>
+ </record>
+ <record model="res.country" id="gs">
+ <field name="name">S. Georgia &amp; S. Sandwich Isls.</field>
+ <field name="code">gs</field>
+ </record>
+ <record model="res.country" id="gt">
+ <field name="name">Guatemala</field>
+ <field name="code">gt</field>
+ </record>
+ <record model="res.country" id="gu">
+ <field name="name">Guam (USA)</field>
+ <field name="code">gu</field>
+ </record>
+ <record model="res.country" id="gw">
+ <field name="name">Guinea Bissau</field>
+ <field name="code">gw</field>
+ </record>
+ <record model="res.country" id="gy">
+ <field name="name">Guyana</field>
+ <field name="code">gy</field>
+ </record>
+ <record model="res.country" id="hk">
+ <field name="name">Hong Kong</field>
+ <field name="code">hk</field>
+ </record>
+ <record model="res.country" id="hm">
+ <field name="name">Heard and McDonald Islands</field>
+ <field name="code">hm</field>
+ </record>
+ <record model="res.country" id="hn">
+ <field name="name">Honduras</field>
+ <field name="code">hn</field>
+ </record>
+ <record model="res.country" id="hr">
+ <field name="name">Croatia</field>
+ <field name="code">hr</field>
+ </record>
+ <record model="res.country" id="ht">
+ <field name="name">Haiti</field>
+ <field name="code">ht</field>
+ </record>
+ <record model="res.country" id="hu">
+ <field name="name">Hungary</field>
+ <field name="code">hu</field>
+ </record>
+ <record model="res.country" id="id">
+ <field name="name">Indonesia</field>
+ <field name="code">id</field>
+ </record>
+ <record model="res.country" id="ie">
+ <field name="name">Ireland</field>
+ <field name="code">ie</field>
+ </record>
+ <record model="res.country" id="il">
+ <field name="name">Israel</field>
+ <field name="code">il</field>
+ </record>
+ <record model="res.country" id="in">
+ <field name="name">India</field>
+ <field name="code">in</field>
+ </record>
+ <record model="res.country" id="io">
+ <field name="name">British Indian Ocean Territory</field>
+ <field name="code">io</field>
+ </record>
+ <record model="res.country" id="iq">
+ <field name="name">Iraq</field>
+ <field name="code">iq</field>
+ </record>
+ <record model="res.country" id="ir">
+ <field name="name">Iran</field>
+ <field name="code">ir</field>
+ </record>
+ <record model="res.country" id="is">
+ <field name="name">Iceland</field>
+ <field name="code">is</field>
+ </record>
+ <record model="res.country" id="it">
+ <field name="name">Italy</field>
+ <field name="code">it</field>
+ </record>
+ <record model="res.country" id="jm">
+ <field name="name">Jamaica</field>
+ <field name="code">jm</field>
+ </record>
+ <record model="res.country" id="jo">
+ <field name="name">Jordan</field>
+ <field name="code">jo</field>
+ </record>
+ <record model="res.country" id="jp">
+ <field name="name">Japan</field>
+ <field name="code">jp</field>
+ </record>
+ <record model="res.country" id="ke">
+ <field name="name">Kenya</field>
+ <field name="code">ke</field>
+ </record>
+ <record model="res.country" id="kg">
+ <field name="name">Kyrgyz Republic (Kyrgyzstan)</field>
+ <field name="code">kg</field>
+ </record>
+ <record model="res.country" id="kh">
+ <field name="name">Cambodia, Kingdom of</field>
+ <field name="code">kh</field>
+ </record>
+ <record model="res.country" id="ki">
+ <field name="name">Kiribati</field>
+ <field name="code">ki</field>
+ </record>
+ <record model="res.country" id="km">
+ <field name="name">Comoros</field>
+ <field name="code">km</field>
+ </record>
+ <record model="res.country" id="kn">
+ <field name="name">Saint Kitts &amp; Nevis Anguilla</field>
+ <field name="code">kn</field>
+ </record>
+ <record model="res.country" id="kp">
+ <field name="name">North Korea</field>
+ <field name="code">kp</field>
+ </record>
+ <record model="res.country" id="kr">
+ <field name="name">South Korea</field>
+ <field name="code">kr</field>
+ </record>
+ <record model="res.country" id="kw">
+ <field name="name">Kuwait</field>
+ <field name="code">kw</field>
+ </record>
+ <record model="res.country" id="ky">
+ <field name="name">Cayman Islands</field>
+ <field name="code">ky</field>
+ </record>
+ <record model="res.country" id="kz">
+ <field name="name">Kazakhstan</field>
+ <field name="code">kz</field>
+ </record>
+ <record model="res.country" id="la">
+ <field name="name">Laos</field>
+ <field name="code">la</field>
+ </record>
+ <record model="res.country" id="lb">
+ <field name="name">Lebanon</field>
+ <field name="code">lb</field>
+ </record>
+ <record model="res.country" id="lc">
+ <field name="name">Saint Lucia</field>
+ <field name="code">lc</field>
+ </record>
+ <record model="res.country" id="li">
+ <field name="name">Liechtenstein</field>
+ <field name="code">li</field>
+ </record>
+ <record model="res.country" id="lk">
+ <field name="name">Sri Lanka</field>
+ <field name="code">lk</field>
+ </record>
+ <record model="res.country" id="lr">
+ <field name="name">Liberia</field>
+ <field name="code">lr</field>
+ </record>
+ <record model="res.country" id="ls">
+ <field name="name">Lesotho</field>
+ <field name="code">ls</field>
+ </record>
+ <record model="res.country" id="lt">
+ <field name="name">Lithuania</field>
+ <field name="code">lt</field>
+ </record>
+ <record model="res.country" id="lu">
+ <field name="name">Luxembourg</field>
+ <field name="code">lu</field>
+ </record>
+ <record model="res.country" id="lv">
+ <field name="name">Latvia</field>
+ <field name="code">lv</field>
+ </record>
+ <record model="res.country" id="ly">
+ <field name="name">Libya</field>
+ <field name="code">ly</field>
+ </record>
+ <record model="res.country" id="ma">
+ <field name="name">Morocco</field>
+ <field name="code">ma</field>
+ </record>
+ <record model="res.country" id="mc">
+ <field name="name">Monaco</field>
+ <field name="code">mc</field>
+ </record>
+ <record model="res.country" id="md">
+ <field name="name">Moldavia</field>
+ <field name="code">md</field>
+ </record>
+ <record model="res.country" id="mg">
+ <field name="name">Madagascar</field>
+ <field name="code">mg</field>
+ </record>
+ <record model="res.country" id="mh">
+ <field name="name">Marshall Islands</field>
+ <field name="code">mh</field>
+ </record>
+ <record model="res.country" id="mk">
+ <field name="name">Macedonia</field>
+ <field name="code">mk</field>
+ </record>
+ <record model="res.country" id="ml">
+ <field name="name">Mali</field>
+ <field name="code">ml</field>
+ </record>
+ <record model="res.country" id="mm">
+ <field name="name">Myanmar</field>
+ <field name="code">mm</field>
+ </record>
+ <record model="res.country" id="mn">
+ <field name="name">Mongolia</field>
+ <field name="code">mn</field>
+ </record>
+ <record model="res.country" id="mo">
+ <field name="name">Macau</field>
+ <field name="code">mo</field>
+ </record>
+ <record model="res.country" id="mp">
+ <field name="name">Northern Mariana Islands</field>
+ <field name="code">mp</field>
+ </record>
+ <record model="res.country" id="mq">
+ <field name="name">Martinique (French)</field>
+ <field name="code">mq</field>
+ </record>
+ <record model="res.country" id="mr">
+ <field name="name">Mauritania</field>
+ <field name="code">mr</field>
+ </record>
+ <record model="res.country" id="ms">
+ <field name="name">Montserrat</field>
+ <field name="code">ms</field>
+ </record>
+ <record model="res.country" id="mt">
+ <field name="name">Malta</field>
+ <field name="code">mt</field>
+ </record>
+ <record model="res.country" id="mu">
+ <field name="name">Mauritius</field>
+ <field name="code">mu</field>
+ </record>
+ <record model="res.country" id="mv">
+ <field name="name">Maldives</field>
+ <field name="code">mv</field>
+ </record>
+ <record model="res.country" id="mw">
+ <field name="name">Malawi</field>
+ <field name="code">mw</field>
+ </record>
+ <record model="res.country" id="mx">
+ <field name="name">Mexico</field>
+ <field name="code">mx</field>
+ </record>
+ <record model="res.country" id="my">
+ <field name="name">Malaysia</field>
+ <field name="code">my</field>
+ </record>
+ <record model="res.country" id="mz">
+ <field name="name">Mozambique</field>
+ <field name="code">mz</field>
+ </record>
+ <record model="res.country" id="na">
+ <field name="name">Namibia</field>
+ <field name="code">na</field>
+ </record>
+ <record model="res.country" id="nc">
+ <field name="name">New Caledonia (French)</field>
+ <field name="code">nc</field>
+ </record>
+ <record model="res.country" id="ne">
+ <field name="name">Niger</field>
+ <field name="code">ne</field>
+ </record>
+ <record model="res.country" id="nf">
+ <field name="name">Norfolk Island</field>
+ <field name="code">nf</field>
+ </record>
+ <record model="res.country" id="ng">
+ <field name="name">Nigeria</field>
+ <field name="code">ng</field>
+ </record>
+ <record model="res.country" id="ni">
+ <field name="name">Nicaragua</field>
+ <field name="code">ni</field>
+ </record>
+ <record model="res.country" id="nl">
+ <field name="name">Netherlands</field>
+ <field name="code">nl</field>
+ </record>
+ <record model="res.country" id="no">
+ <field name="name">Norway</field>
+ <field name="code">no</field>
+ </record>
+ <record model="res.country" id="np">
+ <field name="name">Nepal</field>
+ <field name="code">np</field>
+ </record>
+ <record model="res.country" id="nr">
+ <field name="name">Nauru</field>
+ <field name="code">nr</field>
+ </record>
+ <record model="res.country" id="nt">
+ <field name="name">Neutral Zone</field>
+ <field name="code">nt</field>
+ </record>
+ <record model="res.country" id="nu">
+ <field name="name">Niue</field>
+ <field name="code">nu</field>
+ </record>
+ <record model="res.country" id="nz">
+ <field name="name">New Zealand</field>
+ <field name="code">nz</field>
+ </record>
+ <record model="res.country" id="om">
+ <field name="name">Oman</field>
+ <field name="code">om</field>
+ </record>
+ <record model="res.country" id="pa">
+ <field name="name">Panama</field>
+ <field name="code">pa</field>
+ </record>
+ <record model="res.country" id="pe">
+ <field name="name">Peru</field>
+ <field name="code">pe</field>
+ </record>
+ <record model="res.country" id="pf">
+ <field name="name">Polynesia (French)</field>
+ <field name="code">pf</field>
+ </record>
+ <record model="res.country" id="pg">
+ <field name="name">Papua New Guinea</field>
+ <field name="code">pg</field>
+ </record>
+ <record model="res.country" id="ph">
+ <field name="name">Philippines</field>
+ <field name="code">ph</field>
+ </record>
+ <record model="res.country" id="pk">
+ <field name="name">Pakistan</field>
+ <field name="code">pk</field>
+ </record>
+ <record model="res.country" id="pl">
+ <field name="name">Poland</field>
+ <field name="code">pl</field>
+ </record>
+ <record model="res.country" id="pm">
+ <field name="name">Saint Pierre and Miquelon</field>
+ <field name="code">pm</field>
+ </record>
+ <record model="res.country" id="pn">
+ <field name="name">Pitcairn Island</field>
+ <field name="code">pn</field>
+ </record>
+ <record model="res.country" id="pr">
+ <field name="name">Puerto Rico</field>
+ <field name="code">pr</field>
+ </record>
+ <record model="res.country" id="pt">
+ <field name="name">Portugal</field>
+ <field name="code">pt</field>
+ </record>
+ <record model="res.country" id="pw">
+ <field name="name">Palau</field>
+ <field name="code">pw</field>
+ </record>
+ <record model="res.country" id="py">
+ <field name="name">Paraguay</field>
+ <field name="code">py</field>
+ </record>
+ <record model="res.country" id="qa">
+ <field name="name">Qatar</field>
+ <field name="code">qa</field>
+ </record>
+ <record model="res.country" id="re">
+ <field name="name">Reunion (French)</field>
+ <field name="code">re</field>
+ </record>
+ <record model="res.country" id="ro">
+ <field name="name">Romania</field>
+ <field name="code">ro</field>
+ </record>
+ <record model="res.country" id="ru">
+ <field name="name">Russian Federation</field>
+ <field name="code">ru</field>
+ </record>
+ <record model="res.country" id="rw">
+ <field name="name">Rwanda</field>
+ <field name="code">rw</field>
+ </record>
+ <record model="res.country" id="sa">
+ <field name="name">Saudi Arabia</field>
+ <field name="code">sa</field>
+ </record>
+ <record model="res.country" id="sb">
+ <field name="name">Solomon Islands</field>
+ <field name="code">sb</field>
+ </record>
+ <record model="res.country" id="sc">
+ <field name="name">Seychelles</field>
+ <field name="code">sc</field>
+ </record>
+ <record model="res.country" id="sd">
+ <field name="name">Sudan</field>
+ <field name="code">sd</field>
+ </record>
+ <record model="res.country" id="se">
+ <field name="name">Sweden</field>
+ <field name="code">se</field>
+ </record>
+ <record model="res.country" id="sg">
+ <field name="name">Singapore</field>
+ <field name="code">sg</field>
+ </record>
+ <record model="res.country" id="sh">
+ <field name="name">Saint Helena</field>
+ <field name="code">sh</field>
+ </record>
+ <record model="res.country" id="si">
+ <field name="name">Slovenia</field>
+ <field name="code">si</field>
+ </record>
+ <record model="res.country" id="sj">
+ <field name="name">Svalbard and Jan Mayen Islands</field>
+ <field name="code">sj</field>
+ </record>
+ <record model="res.country" id="sk">
+ <field name="name">Slovak Republic</field>
+ <field name="code">sk</field>
+ </record>
+ <record model="res.country" id="sl">
+ <field name="name">Sierra Leone</field>
+ <field name="code">sl</field>
+ </record>
+ <record model="res.country" id="sm">
+ <field name="name">San Marino</field>
+ <field name="code">sm</field>
+ </record>
+ <record model="res.country" id="sn">
+ <field name="name">Senegal</field>
+ <field name="code">sn</field>
+ </record>
+ <record model="res.country" id="so">
+ <field name="name">Somalia</field>
+ <field name="code">so</field>
+ </record>
+ <record model="res.country" id="sr">
+ <field name="name">Suriname</field>
+ <field name="code">sr</field>
+ </record>
+ <record model="res.country" id="st">
+ <field name="name">Saint Tome (Sao Tome) and Principe</field>
+ <field name="code">st</field>
+ </record>
+ <record model="res.country" id="sv">
+ <field name="name">El Salvador</field>
+ <field name="code">sv</field>
+ </record>
+ <record model="res.country" id="sy">
+ <field name="name">Syria</field>
+ <field name="code">sy</field>
+ </record>
+ <record model="res.country" id="sz">
+ <field name="name">Swaziland</field>
+ <field name="code">sz</field>
+ </record>
+ <record model="res.country" id="tc">
+ <field name="name">Turks and Caicos Islands</field>
+ <field name="code">tc</field>
+ </record>
+ <record model="res.country" id="td">
+ <field name="name">Chad</field>
+ <field name="code">td</field>
+ </record>
+ <record model="res.country" id="tf">
+ <field name="name">French Southern Territories</field>
+ <field name="code">tf</field>
+ </record>
+ <record model="res.country" id="tg">
+ <field name="name">Togo</field>
+ <field name="code">tg</field>
+ </record>
+ <record model="res.country" id="th">
+ <field name="name">Thailand</field>
+ <field name="code">th</field>
+ </record>
+ <record model="res.country" id="tj">
+ <field name="name">Tadjikistan</field>
+ <field name="code">tj</field>
+ </record>
+ <record model="res.country" id="tk">
+ <field name="name">Tokelau</field>
+ <field name="code">tk</field>
+ </record>
+ <record model="res.country" id="tm">
+ <field name="name">Turkmenistan</field>
+ <field name="code">tm</field>
+ </record>
+ <record model="res.country" id="tn">
+ <field name="name">Tunisia</field>
+ <field name="code">tn</field>
+ </record>
+ <record model="res.country" id="to">
+ <field name="name">Tonga</field>
+ <field name="code">to</field>
+ </record>
+ <record model="res.country" id="tp">
+ <field name="name">East Timor</field>
+ <field name="code">tp</field>
+ </record>
+ <record model="res.country" id="tr">
+ <field name="name">Turkey</field>
+ <field name="code">tr</field>
+ </record>
+ <record model="res.country" id="tt">
+ <field name="name">Trinidad and Tobago</field>
+ <field name="code">tt</field>
+ </record>
+ <record model="res.country" id="tv">
+ <field name="name">Tuvalu</field>
+ <field name="code">tv</field>
+ </record>
+ <record model="res.country" id="tw">
+ <field name="name">Taiwan</field>
+ <field name="code">tw</field>
+ </record>
+ <record model="res.country" id="tz">
+ <field name="name">Tanzania</field>
+ <field name="code">tz</field>
+ </record>
+ <record model="res.country" id="ua">
+ <field name="name">Ukraine</field>
+ <field name="code">ua</field>
+ </record>
+ <record model="res.country" id="ug">
+ <field name="name">Uganda</field>
+ <field name="code">ug</field>
+ </record>
+ <record model="res.country" id="uk">
+ <field name="name">United Kingdom</field>
+ <field name="code">uk</field>
+ </record>
+ <record model="res.country" id="um">
+ <field name="name">USA Minor Outlying Islands</field>
+ <field name="code">um</field>
+ </record>
+ <record model="res.country" id="us">
+ <field name="name">United States</field>
+ <field name="code">us</field>
+ </record>
+ <record model="res.country" id="uy">
+ <field name="name">Uruguay</field>
+ <field name="code">uy</field>
+ </record>
+ <record model="res.country" id="uz">
+ <field name="name">Uzbekistan</field>
+ <field name="code">uz</field>
+ </record>
+ <record model="res.country" id="va">
+ <field name="name">Holy See (Vatican City State)</field>
+ <field name="code">va</field>
+ </record>
+ <record model="res.country" id="vc">
+ <field name="name">Saint Vincent &amp; Grenadines</field>
+ <field name="code">vc</field>
+ </record>
+ <record model="res.country" id="ve">
+ <field name="name">Venezuela</field>
+ <field name="code">ve</field>
+ </record>
+ <record model="res.country" id="vg">
+ <field name="name">Virgin Islands (British)</field>
+ <field name="code">vg</field>
+ </record>
+ <record model="res.country" id="vi">
+ <field name="name">Virgin Islands (USA)</field>
+ <field name="code">vi</field>
+ </record>
+ <record model="res.country" id="vn">
+ <field name="name">Vietnam</field>
+ <field name="code">vn</field>
+ </record>
+ <record model="res.country" id="vu">
+ <field name="name">Vanuatu</field>
+ <field name="code">vu</field>
+ </record>
+ <record model="res.country" id="wf">
+ <field name="name">Wallis and Futuna Islands</field>
+ <field name="code">wf</field>
+ </record>
+ <record model="res.country" id="ws">
+ <field name="name">Samoa</field>
+ <field name="code">ws</field>
+ </record>
+ <record model="res.country" id="ye">
+ <field name="name">Yemen</field>
+ <field name="code">ye</field>
+ </record>
+ <record model="res.country" id="yt">
+ <field name="name">Mayotte</field>
+ <field name="code">yt</field>
+ </record>
+ <record model="res.country" id="yu">
+ <field name="name">Yugoslavia</field>
+ <field name="code">yu</field>
+ </record>
+ <record model="res.country" id="za">
+ <field name="name">South Africa</field>
+ <field name="code">za</field>
+ </record>
+ <record model="res.country" id="zm">
+ <field name="name">Zambia</field>
+ <field name="code">zm</field>
+ </record>
+ <!-- DEPRECATED, News name of Zaire is Democratic Republic of the Congo ! -->
+ <record model="res.country" id="zr">
+ <field name="name">Zaire</field>
+ <field name="code">zr</field>
+ </record>
+ <record model="res.country" id="zw">
+ <field name="name">Zimbabwe</field>
+ <field name="code">zw</field>
+ </record>
+
+ <record model="res.partner" id="main_partner">
+ <field name="name">Tiny sprl</field>
+ </record>
+ <record model="res.partner.address" id="main_address">
+ <field name="partner_id" ref="main_partner"/>
+ <field name="name">Fabien Pinckaers</field>
+ <field name="street">Chaussee de Namur 40</field>
+ <field name="zip">1367</field>
+ <field name="city">Gerompont</field>
+ <field name="phone">(+32).81.81.37.00</field>
+ <field name="type">default</field>
+ <field name="country_id" model="res.country" ref="be"/>
+ </record>
+
+#
+# Currencies
+#
+
+ <record model="res.currency" id="EUR">
+ <field name="name">EUR</field>
+ <field name="code">EUR</field>
+ <field name="rate">1.0</field>
+ <field name="rounding">2</field>
+ <field name="accuracy">4</field>
+ </record>
+
+ <record model="res.company" id="main_company">
+ <field name="name">Tiny sprl</field>
+ <field name="partner_id" ref="main_partner"/>
+ <field name="rml_header1">Free Business Solutions</field>
+ <field name="rml_footer1">Web: http://tiny.be - Tel: (+32).81.81.37.00 - Bank: CPH 126-2013269-07</field>
+ <field name="rml_footer2">IBAN: BE74 1262 0132 6907 - SWIFT: GKCCBEBB - VAT: BE0477.472.701</field>
+ <field name="currency_id" ref="EUR"/>
+ </record>
+
+ <record model="res.users" id="user_admin">
+ <field name="login">admin</field>
+ <field name="password">admin</field>
+ <field name="name">Administrator</field>
+ <field name="signature">Administrator</field>
+ <field name="action_id" ref="action_menu_admin"/>
+ <field name="address_id" ref="main_address"/>
+ <field name="groups_id" eval="[(6,0,[group_admin])]"/>
+ <field name="company_id" ref="main_company"/>
+ </record>
+</data>
+</terp>
diff --git a/bin/addons/base/base_demo.xml b/bin/addons/base/base_demo.xml
new file mode 100644
index 00000000000..361fe5e5718
--- /dev/null
+++ b/bin/addons/base/base_demo.xml
@@ -0,0 +1,274 @@
+<?xml version="1.0"?>
+<terp>
+<data noupdate="1">
+ <record model="res.users" id="user_demo">
+ <field name="login">demo</field>
+ <field name="password">demo</field>
+ <field name="name">Demo User</field>
+ <field name="signature">Fabien Pinckaers</field>
+ <field name="action_id" ref="action_menu_admin"/>
+ <field name="address_id" ref="main_address"/>
+ <field name="company_id" ref="main_company"/>
+ </record>
+
+ <record model="res.currency" id="USD">
+ <field name="name">USD</field>
+ <field name="rate">0.8400</field>
+ <field name="rounding">2</field>
+ <field name="accuracy">4</field>
+ </record>
+
+
+ <!-- USA States -->
+ <record model="res.country.state" id="us-al">
+ <field name="name">Alabama</field>
+ <field name="code">al</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ak">
+ <field name="name">Alaska</field>
+ <field name="code">ak</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-az">
+ <field name="name">Arizona</field>
+ <field name="code">az</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ar">
+ <field name="name">Arkansas</field>
+ <field name="code">ar</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ca">
+ <field name="name">California</field>
+ <field name="code">ca</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-co">
+ <field name="name">Colorado</field>
+ <field name="code">co</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ct">
+ <field name="name">Connecticut</field>
+ <field name="code">ct</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-de">
+ <field name="name">Delaware</field>
+ <field name="code">de</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-fl">
+ <field name="name">Florida</field>
+ <field name="code">fl</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ga">
+ <field name="name">Georgia</field>
+ <field name="code">ga</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-hi">
+ <field name="name">Hawaii</field>
+ <field name="code">hi</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-id">
+ <field name="name">Idaho</field>
+ <field name="code">id</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-il">
+ <field name="name">Illinois</field>
+ <field name="code">il</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-in">
+ <field name="name">Indiana</field>
+ <field name="code">in</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ia">
+ <field name="name">Iowa</field>
+ <field name="code">ia</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ks">
+ <field name="name">Kansas</field>
+ <field name="code">ks</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ky">
+ <field name="name">Kentuky</field>
+ <field name="code">ky</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-la">
+ <field name="name">Louisiana</field>
+ <field name="code">la</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-me">
+ <field name="name">Maine</field>
+ <field name="code">me</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-md">
+ <field name="name">Maryland</field>
+ <field name="code">md</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ma">
+ <field name="name">Massachusetts</field>
+ <field name="code">ma</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-mi">
+ <field name="name">Michigan</field>
+ <field name="code">mi</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-mn">
+ <field name="name">Minnesota</field>
+ <field name="code">mn</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ms">
+ <field name="name">Mississippi</field>
+ <field name="code">ms</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-mo">
+ <field name="name">Missouri</field>
+ <field name="code">mo</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-mt">
+ <field name="name">Montana</field>
+ <field name="code">mt</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ne">
+ <field name="name">Nebraska</field>
+ <field name="code">ne</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-nv">
+ <field name="name">Nevada</field>
+ <field name="code">nv</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-nh">
+ <field name="name">New Hampshire</field>
+ <field name="code">nh</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-nj">
+ <field name="name">New Jersey</field>
+ <field name="code">nj</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-nm">
+ <field name="name">New Mexico</field>
+ <field name="code">nm</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ny">
+ <field name="name">New York</field>
+ <field name="code">ny</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-nc">
+ <field name="name">North Carolina</field>
+ <field name="code">nc</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-nd">
+ <field name="name">New Dakota</field>
+ <field name="code">nd</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-oh">
+ <field name="name">Ohio</field>
+ <field name="code">oh</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ok">
+ <field name="name">Oklahoma</field>
+ <field name="code">ok</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-or">
+ <field name="name">Oregon</field>
+ <field name="code">or</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-pa">
+ <field name="name">Pennsylviania</field>
+ <field name="code">pa</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ri">
+ <field name="name">Rhode Island</field>
+ <field name="code">ri</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-sc">
+ <field name="name">South Carolina</field>
+ <field name="code">sc</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-sd">
+ <field name="name">South Dakota</field>
+ <field name="code">sd</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-tn">
+ <field name="name">Tennessee</field>
+ <field name="code">tn</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-tx">
+ <field name="name">Texas</field>
+ <field name="code">tx</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-ut">
+ <field name="name">Utah</field>
+ <field name="code">ut</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-vt">
+ <field name="name">Vermont</field>
+ <field name="code">vt</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-va">
+ <field name="name">Virgnia</field>
+ <field name="code">va</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-wa">
+ <field name="name">Washington</field>
+ <field name="code">wa</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-wv">
+ <field name="name">West Virgnia</field>
+ <field name="code">wv</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-wi">
+ <field name="name">Wisconsin</field>
+ <field name="code">wi</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+ <record model="res.country.state" id="us-wy">
+ <field name="name">Wyoming</field>
+ <field name="code">wy</field>
+ <field name="country_id" model="res.country" ref="us"/>
+ </record>
+</data>
+</terp>
diff --git a/bin/addons/base/base_menu.xml b/bin/addons/base/base_menu.xml
new file mode 100644
index 00000000000..138d6470db1
--- /dev/null
+++ b/bin/addons/base/base_menu.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<terp>
+<data>
+<!-- <menuitem name="Tools" sequence="15" groups="admin" icon="terp-tools"/> -->
+ <menuitem name="Administration" sequence="20" groups="admin" icon="terp-administration"/>
+
+ # Admin config
+
+ <menuitem name="Administration/Configuration/Base" sequence="1"/>
+
+ <record model="ir.actions.act_window" id="open_module_tree_company">
+ <field name="name">res.company</field>
+ <field name="res_model">res.company</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem name="Administration/Configuration/Base/Define Main Company" action="open_module_tree_company" sequence="2" id="menu_company_def"/>
+
+</data>
+</terp>
diff --git a/bin/addons/base/base_update.xml b/bin/addons/base/base_update.xml
new file mode 100644
index 00000000000..2f7ee01adfe
--- /dev/null
+++ b/bin/addons/base/base_update.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0"?>
+<terp>
+<data>
+ <!--
+ ======================
+ Languages
+ ======================
+ -->
+ <record model="ir.ui.view" id="view_lang">
+ <field name="name">Languages</field>
+ <field name="model">res.lang</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Language">
+ <field name="name" select="1"/>
+ <field name="code" select="1"/>
+ <field name="translatable"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_lang">
+ <field name="name">res.lang</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.lang</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Administration/Custom/Interface/Languages" action="action_lang"/>
+
+ <ir_set>
+ <field name="key" eval="'meta'"/>
+ <field name="key2" eval="'lang'"/>
+ <field name="models" eval="['res.users']"/>
+ <field name="name">lang</field>
+ <field name="value" eval="False"/>
+ <field name="meta" eval="{'type':'selection', 'string':'Language', 'selection':[(False,'en')]}"/>
+ <field name="replace" eval="True"/>
+ </ir_set>
+
+ <!--
+ ======================
+ Groups
+ ======================
+ -->
+ <record model="ir.ui.view" id="view_groups_form">
+ <field name="name">res.groups.form</field>
+ <field name="model">res.groups</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Groups">
+ <field name="name" colspan="3" select="1"/>
+ </form>
+ </field>
+ </record>
+
+ <!--
+ ======================
+ Users
+ ======================
+ -->
+ <record model="ir.ui.view" id="view_users_form_simple_modif">
+ <field name="name">res.users.form.modif</field>
+ <field name="model">res.users</field>
+ <field name="type">form</field>
+ <field name="priority" eval="10"/>
+ <field name="arch" type="xml">
+ <form string="Users">
+ <field name="name" colspan="3" select="1"/>
+ <field name="login" readonly="1" select="1"/>
+ <field name="password"/>
+ <label string="Please note that you will have to logout and relog if you change your password." colspan="4"/>
+ <newline/>
+ <field name="signature" colspan="3"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_users_form_simple">
+ <field name="name">res.users.form</field>
+ <field name="model">res.users</field>
+ <field name="type">form</field>
+ <field name="priority" eval="8"/>
+ <field name="arch" type="xml">
+ <form string="Users">
+ <field name="name" readonly="1" colspan="3" select="1"/>
+ <field name="login" readonly="1" select="1"/>
+ <newline/>
+ <field name="signature" readonly="1" colspan="3"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_users_form">
+ <field name="name">res.users.form</field>
+ <field name="model">res.users</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Users">
+ <field name="name" select="1"/>
+ <field name="active" select="1"/>
+ <field name="login" select="1"/>
+ <field name="password"/>
+ <field name="signature"/>
+ <field name="address_id"/>
+ <field name="company_id" required="1"/>
+ <field name="action_id" domain="[('usage','=','menu')]" required="True"/>
+ <newline/>
+ <field name="groups_id"/>
+ <field name="roles_id"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_users_tree">
+ <field name="name">res.users.tree</field>
+ <field name="model">res.users</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Users">
+ <field name="name"/>
+ <field name="login"/>
+ </tree>
+ </field>
+ </record>
+
+ <!--
+ ======================
+ Company
+ ======================
+ -->
+ <record model="ir.ui.view" id="view_company_form">
+ <field name="name">res.company.form</field>
+ <field name="model">res.company</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Company">
+ <field name="name" colspan="3" select="1"/>
+ <field name="partner_id" select="1"/>
+ <field name="parent_id" select="1"/>
+ <field name="rml_header1"/>
+ <field name="rml_footer1"/>
+ <field name="rml_footer2"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_company_tree">
+ <field name="name">res.company.tree</field>
+ <field name="model">res.company</field>
+ <field name="type">tree</field>
+ <field name="field_parent">child_ids</field>
+ <field name="arch" type="xml">
+ <tree string="Companies">
+ <field name="name"/>
+ <field name="partner_id"/>
+ </tree>
+ </field>
+ </record>
+
+ # Admin config
+
+ <menuitem name="Administration/Configuration/User" sequence="4"/>
+
+ <record model="ir.actions.act_window" id="open_module_users_list">
+ <field name="name">res.users.list</field>
+ <field name="res_model">res.users</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" ref="base.view_users_tree"/>
+ </record>
+ <menuitem name="Administration/Configuration/User/Define Users" action="open_module_users_list" sequence="1" id="menu_users_list"/>
+
+</data>
+</terp>
diff --git a/bin/addons/base/ir/__init__.py b/bin/addons/base/ir/__init__.py
new file mode 100644
index 00000000000..46331d821fd
--- /dev/null
+++ b/bin/addons/base/ir/__init__.py
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import ir_sequence
+import ir_ui_menu
+import ir_ui_view
+import ir_actions
+import ir_default
+import ir_model
+import ir_report_custom
+import ir_attachment
+import ir_cron
+import ir_values
+import ir_translation
+import ir_exports
+import workflow
diff --git a/bin/addons/base/ir/ir.xml b/bin/addons/base/ir/ir.xml
new file mode 100644
index 00000000000..049b229ae87
--- /dev/null
+++ b/bin/addons/base/ir/ir.xml
@@ -0,0 +1,654 @@
+<?xml version="1.0"?>
+<terp>
+<data>
+ ==========================================================
+ Sequences
+ ==========================================================
+
+ <record model="ir.ui.view" id="sequence_view">
+ <field name="name">ir.sequence.form</field>
+ <field name="model">ir.sequence</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Sequences">
+ <separator string="Configuration" colspan="4"/>
+ <field name="name" colspan="3" select="1"/>
+ <field name="code" select="1"/>
+ <field name="active" select="1"/>
+ <field name="prefix"/>
+ <field name="suffix"/>
+ <field name="padding" />
+ <field name="number_increment"/>
+ <field name="number_next"/>
+ <separator string="Legend (for prefix, suffix)" colspan="4"/>
+ <label string="Year: %%(year)s" colspan="4"/>
+ <label string="Month: %%(month)s" colspan="4"/>
+ <label string="Day: %%(day)s" colspan="4"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="ir_sequence_form">
+ <field name="name">ir.sequence</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.sequence</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="sequence_view"/>
+ </record>
+ <menuitem name="Administration/Custom/Sequences/Sequences" action="ir_sequence_form"/>
+
+ ==========================================================
+ Sequences Types
+ ==========================================================
+
+ <record model="ir.ui.view" id="sequence_type_form_view">
+ <field name="name">ir.sequence.type.form</field>
+ <field name="model">ir.sequence.type</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Sequence Type">
+ <field name="name" select="1"/>
+ <field name="code" readonly="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="ir_sequence_type">
+ <field name="name">ir.sequence.type</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.sequence.type</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" eval="False"/>
+ </record>
+ <menuitem name="Administration/Custom/Sequences/Sequence Types" action="ir_sequence_type"/>
+
+ ==========================================================
+ Actions
+ ==========================================================
+
+ <record model="ir.actions.act_window" id="ir_sequence_actions">
+ <field name="name">ir.actions.actions</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.actions.actions</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Actions/Actions" action="ir_sequence_actions"/>
+
+ <record model="ir.actions.act_window" id="ir_action_execute">
+ <field name="name">ir.actions.execute</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.actions.execute</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Actions/Execute" action="ir_action_execute"/>
+
+ <record model="ir.actions.act_window" id="ir_action_group">
+ <field name="name">ir.actions.group</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.actions.group</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Actions/Group" action="ir_action_group"/>
+
+ <record model="ir.actions.act_window" id="ir_action_report_custom">
+ <field name="name">ir.actions.report.custom</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.actions.report.custom</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Actions/Report Custom" action="ir_action_report_custom"/>
+
+ <record model="ir.actions.act_window" id="ir_action_report_xml">
+ <field name="name">ir.actions.report.xml</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.actions.report.xml</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Actions/Report Xml" action="ir_action_report_xml"/>
+
+ <record model="ir.ui.view" id="view_window_action_tree">
+ <field name="name">ir.actions.windows.tree</field>
+ <field name="model">ir.actions.act_window</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Window Action">
+ <field name="name"/>
+ <field name="res_model"/>
+ <field name="view_type"/>
+ <field name="view_id"/>
+ <field name="domain"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="view_window_action_form">
+ <field name="name">ir.actions.windows.form</field>
+ <field name="model">ir.actions.act_window</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Open a Window">
+ <field name="name" colspan="3" select="1"/>
+ <field name="type" readonly="1"/>
+ <field name="view_id"/>
+ <field name="res_model"/>
+ <field name="view_type"/>
+ <field name="domain" colspan="3"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="ir_action_window">
+ <field name="name">ir.actions.act_window</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.actions.act_window</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_window_action_form"/>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Actions/Open Window" action="ir_action_window"/>
+
+ <record model="ir.actions.act_window" id="ir_action_wizard">
+ <field name="name">ir.actions.wizard</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.actions.wizard</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Actions/Wizard" action="ir_action_wizard"/>
+
+ ==========================================================
+ Companies
+ ==========================================================
+
+ <record model="ir.actions.act_window" id="action_res_company_tree">
+ <field name="name">res.company</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.company</field>
+ <field name="domain">[('parent_id','=',False)]</field>
+ <field name="view_type">tree</field>
+ </record>
+ <menuitem name="Administration/Users/Company Structure" action="action_res_company_tree"/>
+ <record model="ir.actions.act_window" id="action_res_company_form">
+ <field name="name">res.groups</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.company</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Administration/Users/Company Structure/Define Companies" action="action_res_company_form"/>
+
+ ==========================================================
+ User Roles
+ ==========================================================
+
+ <record model="ir.actions.act_window" id="action_res_users">
+ <field name="name">res.users</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.users</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_users_form"/>
+ </record>
+ <menuitem name="Administration/Users/Users" action="action_res_users"/>
+ <record model="ir.actions.act_window" id="action_res_users_my">
+ <field name="name">res.users</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.users</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="domain">[('id','=',uid)]</field>
+ <field name="view_id" ref="view_users_form_simple_modif"/>
+ </record>
+ <menuitem name="Administration/Users/Users/My password" action="action_res_users_my"/>
+
+ <record model="ir.actions.act_window" id="action_res_groups">
+ <field name="name">res.groups</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.groups</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Administration/Users/Groups" action="action_res_groups"/>
+
+ <record model="ir.ui.view" id="view_roles_form">
+ <field name="name">res.roles.form</field>
+ <field name="model">res.roles</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Role">
+ <field name="name" colspan="3" select="1"/>
+ <field name="parent_id" colspan="3"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_roles_tree">
+ <field name="name">res.roles.tree</field>
+ <field name="model">res.roles</field>
+ <field name="type">tree</field>
+ <field name="field_parent">child_id</field>
+ <field name="arch" type="xml">
+ <tree string="Roles">
+ <field name="name"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_res_roles">
+ <field name="name">res.roles.tree</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.roles</field>
+ <field name="view_type">tree</field>
+ <field name="domain" eval="[('parent_id','=',False)]"/>
+ </record>
+ <menuitem name="Administration/Users/Roles structure" action="action_res_roles"/>
+ <record model="ir.actions.act_window" id="action_res_roles_form">
+ <field name="name">res.roles.tree</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.roles</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Administration/Users/Roles structure/Define Roles" action="action_res_roles_form"/>
+
+ ==========================================================
+ View
+ ==========================================================
+ <record model="ir.ui.view" id="view_view_form">
+ <field name="name">ir.ui.view</field>
+ <field name="model">ir.ui.view</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="User Interface - Views">
+ <field name="name" select="1"/>
+ <field name="type"/>
+ <field name="model" select="1"/>
+ <field name="field_parent"/>
+ <field name="priority"/>
+ <field name="inherit_id"/>
+ <field name="arch" colspan="3" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_ui_view">
+ <field name="name">ir.ui.view</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.ui.view</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_view_form"/>
+ </record>
+ <menuitem name="Administration/Custom/Interface/View" action="action_ui_view"/>
+
+ ==========================================================
+ Attachment
+ ==========================================================
+ <record model="ir.ui.view" id="view_attachment_form">
+ <field name="name">ir.attachment.view</field>
+ <field name="model">ir.attachment</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Attachments">
+ <field name="name" colspan="3" select="1"/>
+ <field name="description" colspan="3"/>
+ <field name="datas" colspan="3"/>
+ <field name="datas_fname"/>
+ <newline/>
+ <field name="res_model" select="1"/>
+ <field name="res_id"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_attachment">
+ <field name="name">ir.attachment</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.attachment</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_attachment_form"/>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Attachments" action="action_attachment"/>
+
+ ==========================================================
+ Report
+ ==========================================================
+ <record model="ir.ui.view" id="view_report_custom_fields_tree">
+ <field name="name">ir.report.custom.fields.tree</field>
+ <field name="model">ir.report.custom.fields</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Report Fields">
+ <field name="sequence"/>
+ <field name="name"/>
+ <field name="width"/>
+ <field name="field_child0"/>
+ <field name="field_child1"/>
+ <field name="field_child2"/>
+ <field name="field_child3"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="view_report_custom_fields_form">
+ <field name="name">ir.report.custom.fields</field>
+ <field name="model">ir.report.custom.fields</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Report Fields">
+ <field name="name"/>
+ <field name="alignment"/>
+ <field name="width"/>
+ <field name="sequence"/>
+ <newline/>
+ <field name="bgcolor"/>
+ <field name="fontcolor"/>
+ <newline/>
+ <field name="operation"/>
+ <field name="groupby"/>
+ <field name="cumulate"/>
+ <newline/>
+ <field name="field_child0" domain="[('model_id','=',parent['model_id'])]" on_change="onchange_field_child0(field_child0)"/>
+ <field name="fc0_operande" domain="[('model_id','=',parent['model_id'])]"/>
+ <field name="fc0_op"/>
+ <field name="fc0_condition"/>
+ <newline/>
+ <field name="field_child1" on_change="onchange_field_child1(field_child1)"/>
+ <field name="fc1_operande"/>
+ <field name="fc1_op"/>
+ <field name="fc1_condition"/>
+ <newline/>
+ <field name="field_child2" on_change="onchange_field_child2(field_child2)"/>
+ <field name="fc2_operande"/>
+ <field name="fc2_op"/>
+ <field name="fc2_condition"/>
+ <newline/>
+ <field name="field_child3"/>
+ <field name="fc3_operande"/>
+ <field name="fc3_op"/>
+ <field name="fc3_condition"/>
+
+ </form>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="view_report_custom_form">
+ <field name="name">ir.report.custom</field>
+ <field name="model">ir.report.custom</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Custom Report">
+ <field name="name" select="1"/>
+ <field name="model_id" select="1" on_change="onchange_model_id(model_id)"/>
+ <field name="title"/>
+ <field name="type"/>
+ <field name="print_format"/>
+ <field name="print_orientation"/>
+ <field name="repeat_header"/>
+ <field name="footer"/>
+ <field name="sortby"/>
+ <field name="field_parent"/>
+ <field name="frequency"/>
+ <field name="limitt"/>
+ <field name="menu_id" />
+ <field name="fields_child0" widget="one2many_list" colspan="3"/>
+ <field name="state" readonly="1" select="1"/>
+ <group col="2" colspan="2">
+ <button name="subscribe" string="Subscribe Report" states="unsubscribed" type="object"/>
+ <button name="unsubscribe" string="Unsubscribe Report" states="subscribed" type="object"/>
+ </group>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_report_custom">
+ <field name="name">ir.report.custom</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.report.custom</field>
+ <field name="view_type">tree</field>
+ <field name="view_id" ref="view_report_custom_form"/>
+ </record>
+ <!--
+ <menuitem name="Administration/Report/Report Custom" action="action_report_custom"/>
+ -->
+
+ ==========================================================
+ model
+ ==========================================================
+ <record model="ir.ui.view" id="view_model_form">
+ <field name="name">ir.model.form</field>
+ <field name="model">ir.model</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Model Description">
+ <field name="name" select="1"/>
+ <field name="model" select="1"/>
+ <field name="info" select="1" colspan="3"/>
+ <separator string="Fields" colspan="4"/>
+ <field name="field_id" colspan="4" nolabel="1"/>
+ </form>
+ </field>
+ </record>
+
+
+ <record model="ir.ui.view" id="view_model_tree">
+ <field name="name">ir.model.tree</field>
+ <field name="model">ir.model</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Model Description">
+ <field name="name"/>
+ <field name="model"/>
+ </tree>
+ </field>
+ </record>
+
+
+ ==========================================================
+ fields_description
+ ==========================================================
+ <record model="ir.ui.view" id="view_model_fields_form">
+ <field name="name">ir.model.fields.form</field>
+ <field name="model">ir.model.fields</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Fields Description">
+ <field name="name" select="1"/>
+ <field name="model_id"/>
+ <field name="field_description" select="1" colspan="4"/>
+ <field name="ttype" select="1"/>
+ <field name="relation" select="1"/>
+ <field name="relate" select="1"/>
+ </form>
+ </field>
+ </record>
+
+
+ <record model="ir.ui.view" id="ir_model_fields_tree">
+ <field name="name">ir.model.fields.tree</field>
+ <field name="model">ir.model.fields</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Fields Description">
+ <field name="name" select="1"/>
+ <field name="field_description" select="1"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.actions.act_window" id="action_model_model">
+ <field name="name">ir.model.form</field>
+ <field name="res_model">ir.model</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_model_form"/>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Database Structure/Objects" action="action_model_model" id="ir_model_model_menu"/>
+
+ <record model="ir.actions.act_window" id="action_model_fields">
+ <field name="name">ir.model.fields.form</field>
+ <field name="res_model">ir.model.fields</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_model_fields_form"/>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Database Structure/Fields" action="action_model_fields" id="ir_model_model_fields"/>
+
+ ==========================================================
+ Translations
+ ==========================================================
+
+ <record model="ir.ui.view" id="view_translation_form">
+ <field name="name">Translations</field>
+ <field name="model">ir.translation</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Translations">
+ <field name="name" select="1"/>
+ <field name="lang" select="1"/>
+ <field name="src" colspan="3" select="1"/>
+ <field name="type" select="1"/>
+ <field name="res_id" select="1"/>
+ <field name="value" colspan="3" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_translation">
+ <field name="name">ir.translation.view</field>
+ <field name="res_model">ir.translation</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_translation_form"/>
+ </record>
+ <menuitem name="Administration/Translations/All terms" action="action_translation"/>
+
+ <record model="ir.ui.view" id="view_translation_tree">
+ <field name="name">Translations</field>
+ <field name="model">ir.translation</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Translations">
+ <field name="name"/>
+ <field name="lang"/>
+ <field name="src"/>
+ <field name="type"/>
+ </tree>
+ </field>
+ </record>
+
+ <!--
+ =============================================================
+ Menu Edition
+ =============================================================
+ -->
+
+ <record model="ir.ui.view" id="edit_menu">
+ <field name="name">ir.ui.menu.tree</field>
+ <field name="model">ir.ui.menu</field>
+ <field name="type">tree</field>
+ <field name="priority" eval="8"/>
+ <field name="arch" type="xml">
+ <tree string="Menu">
+ <field name="complete_name" icon="icon"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="edit_menu_access">
+ <field name="name">ir.ui.menu.form2</field>
+ <field name="model">ir.ui.menu</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Menu">
+ <field name="complete_name"/>
+ <field name="name" select="1"/>
+ <field name="sequence"/>
+ <field name="parent_id" colspan="3" select="1"/>
+ <field name="groups_id" colspan="3"/>
+ <field name="icon"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="grant_menu_access">
+ <field name="name">ir.ui.menu.form2</field>
+ <field name="res_model">ir.ui.menu</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="edit_menu_access"/>
+ </record>
+ <menuitem name="Administration/Security/Grant Access to menu"
+ action="grant_menu_access" />
+
+ <!--
+ =============================================================
+ Cron Jobs
+ =============================================================
+ -->
+ <record model="ir.ui.view" id="ir_cron_view_tree">
+ <field name="name">ir.cron.tree</field>
+ <field name="model">ir.cron</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Scheduled Actions">
+ <field name="nextcall"/>
+ <field name="name"/>
+ <field name="interval_number"/>
+ <field name="interval_type"/>
+ <field name="numbercall"/>
+ </tree>
+ </field>
+ </record> <record model="ir.ui.view" id="ir_cron_view">
+ <field name="name">ir.cron.form</field>
+ <field name="model">ir.cron</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Scheduled Actions">
+ <field name="name" select="1"/>
+ <field name="active" select="1"/>
+ <field name="user_id" select="1"/>
+ <field name="priority" select="1"/>
+ <newline/>
+ <field name="interval_number"/>
+ <field name="interval_type"/>
+ <newline/>
+ <field name="nextcall"/>
+ <field name="numbercall"/>
+ <field name="doall"/>
+ <separator string="Action to trigger" colspan="4"/>
+ <newline/>
+ <field name="model" readonly="1"/>
+ <field name="function" readonly="1"/>
+ <field name="args" colspan="3" readonly="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="ir_cron_act">
+ <field name="name">ir.cron.form</field>
+ <field name="res_model">ir.cron</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="ir_cron_view"/>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Actions/Scheduled Actions"
+ action="ir_cron_act"/>
+
+
+ <record model="ir.ui.view" id="ir_access_view_tree">
+ <field name="name">ir.model.access.tree</field>
+ <field name="model">ir.model.access</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Access Controls">
+ <field name="name"/>
+ <field name="model_id"/>
+ <field name="group_id"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="ir_access_view_form">
+ <field name="name">ir.model.access.form</field>
+ <field name="model">ir.model.access</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Access Controls">
+ <field name="name" select="1" colspan="3"/>
+ <field name="model_id" select="1"/>
+ <field name="group_id" select="1"/>
+ <newline/>
+ <field name="perm_read"/>
+ <field name="perm_write"/>
+ <field name="perm_create"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="ir_access_act">
+ <field name="name">ir.model.access.form</field>
+ <field name="res_model">ir.model.access</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="ir_access_view_form"/>
+ </record>
+ <menuitem name="Administration/Security/Access Controls" action="ir_access_act"/>
+
+</data>
+</terp>
diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py
new file mode 100644
index 00000000000..bc260d149fa
--- /dev/null
+++ b/bin/addons/base/ir/ir_actions.py
@@ -0,0 +1,141 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+
+class actions(osv.osv):
+ _name = 'ir.actions.actions'
+ _table = 'ir_actions'
+ _columns = {
+ 'name': fields.char('Action Name', required=True, size=64),
+ 'type': fields.char('Action Type', required=True, size=32),
+ 'usage': fields.char('Action Usage', size=32)
+ }
+ _defaults = {
+ 'usage': lambda *a: False,
+ }
+actions()
+
+class act_execute(osv.osv):
+ _name = 'ir.actions.execute'
+ _table = 'ir_act_execute'
+ _sequence = 'ir_actions_id_seq'
+ _columns = {
+ 'name': fields.char('name', size=64, required=True, translate=True),
+ 'type': fields.char('type', size=32, required=True),
+ 'func_name': fields.char('Function Name', size=64, required=True),
+ 'func_arg': fields.char('Function Argument', size=64),
+ 'usage': fields.char('Action Usage', size=32)
+ }
+act_execute()
+
+class group(osv.osv):
+ _name = 'ir.actions.group'
+ _table = 'ir_act_group'
+ _sequence = 'ir_actions_id_seq'
+ _columns = {
+ 'name': fields.char('Group Name', size=64, required=True),
+ 'type': fields.char('Action Type', size=32, required=True),
+ 'exec_type': fields.char('Execution sequence', size=64, required=True),
+ 'usage': fields.char('Action Usage', size=32)
+ }
+group()
+
+class report_custom(osv.osv):
+ _name = 'ir.actions.report.custom'
+ _table = 'ir_act_report_custom'
+ _sequence = 'ir_actions_id_seq'
+ _columns = {
+ 'name': fields.char('Report Name', size=64, required=True, translate=True),
+ 'type': fields.char('Report Type', size=32, required=True),
+ 'model':fields.char('Model', size=64, required=True),
+ 'report_id': fields.integer('Report Ref.', required=True),
+ 'usage': fields.char('Action Usage', size=32)
+ }
+report_custom()
+
+class report_xml(osv.osv):
+ _name = 'ir.actions.report.xml'
+ _table = 'ir_act_report_xml'
+ _sequence = 'ir_actions_id_seq'
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True, translate=True),
+ 'type': fields.char('Report Type', size=32, required=True),
+ 'model': fields.char('Model', size=64, required=True),
+ 'report_name': fields.char('Internal Name', size=64, required=True),
+ 'report_xsl': fields.char('XSL path', size=256),
+ 'report_xml': fields.char('XML path', size=256),
+ 'report_rml': fields.char('RML path', size=256),
+ 'auto': fields.boolean('Automatic XSL:RML', required=True),
+ 'usage': fields.char('Action Usage', size=32)
+ }
+ _defaults = {
+ 'type': lambda *a: 'ir.actions.report.xml',
+ 'auto': lambda *a: True,
+ }
+report_xml()
+
+class act_window(osv.osv):
+ _name = 'ir.actions.act_window'
+ _table = 'ir_act_window'
+ _sequence = 'ir_actions_id_seq'
+ _columns = {
+ 'name': fields.char('Action Name', size=64, required=True, translate=True),
+ 'type': fields.char('Action Type', size=32, required=True),
+ 'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
+ 'domain': fields.char('Domain Value', size=250),
+ 'context': fields.char('Context Value', size=250),
+ 'res_model': fields.char('Model', size=64),
+ 'view_type': fields.selection((('tree','Tree'),('form','Form')),string='Type of view'),
+ 'view_mode': fields.selection((('form,list','Form - List'),('list,form','List - Form')), string='Mode of view'),
+ 'usage': fields.char('Action Usage', size=32)
+ }
+ _defaults = {
+ 'type': lambda *a: 'ir.actions.act_window',
+ 'view_type': lambda *a: 'form',
+ 'view_mode': lambda *a: 'form,tree',
+ 'context': lambda *a: '{}'
+ }
+act_window()
+
+class act_wizard(osv.osv):
+ _name = 'ir.actions.wizard'
+ _table = 'ir_act_wizard'
+ _sequence = 'ir_actions_id_seq'
+ _columns = {
+ 'name': fields.char('Wizard info', size=64, required=True, translate=True),
+ 'type': fields.char('Action type', size=32, required=True),
+ 'wiz_name': fields.char('Wizard name', size=64, required=True),
+ 'multi': fields.boolean('Action on multiple doc.', help="If set to true, the wizard will not be displayed on the right toolbar of a form views.")
+ }
+ _defaults = {
+ 'type': lambda *a: 'ir.actions.wizard',
+ 'multi': lambda *a: False,
+ }
+act_wizard()
+
diff --git a/bin/addons/base/ir/ir_attachment.py b/bin/addons/base/ir/ir_attachment.py
new file mode 100644
index 00000000000..86078c4d090
--- /dev/null
+++ b/bin/addons/base/ir/ir_attachment.py
@@ -0,0 +1,43 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+
+class ir_attachment(osv.osv):
+ _name = 'ir.attachment'
+ _columns = {
+ 'name': fields.char('Attachment Name',size=64, required=True),
+ 'datas': fields.binary('Data'),
+ 'datas_fname': fields.char('Data Filename',size=64),
+ 'description': fields.text('Description'),
+ 'res_model': fields.char('Resource Model',size=64, required=True, readonly=True),
+ 'res_id': fields.integer('Resource ID', required=True, readonly=True),
+ 'link': fields.char('Link', size=256)
+}
+ir_attachment()
+
diff --git a/bin/addons/base/ir/ir_cron.py b/bin/addons/base/ir/ir_cron.py
new file mode 100644
index 00000000000..73e1e2000fe
--- /dev/null
+++ b/bin/addons/base/ir/ir_cron.py
@@ -0,0 +1,139 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+#
+# SPEC: Execute "model.function(*eval(args))" periodically
+# date : date to execute the job or NULL if directly
+# delete_after: delete the ir.cron entry after execution
+# interval_* : period
+# max_repeat : number of execution or NULL if endlessly
+#
+# TODO:
+# Error treatment: exception, request, ... -> send request to uid
+#
+
+from mx import DateTime
+import time
+import netsvc
+import tools
+import pooler
+from osv import fields,osv
+
+next_wait = 60
+
+_intervalTypes = {
+ 'work_days': lambda interal: DateTime.RelativeDateTime(days=interval),
+ 'days': lambda interval: DateTime.RelativeDateTime(days=interval),
+ 'hours': lambda interval: DateTime.RelativeDateTime(hours=interval),
+ 'weeks': lambda interval: DateTime.RelativeDateTime(days=7*interval),
+ 'months': lambda interval: DateTime.RelativeDateTime(months=interval),
+ 'minutes': lambda interval: DateTime.RelativeDateTime(minutes=interval),
+}
+
+class ir_cron(osv.osv, netsvc.Agent):
+ _name = "ir.cron"
+ _columns = {
+ 'name': fields.char('Name', size=60, required=True),
+ 'user_id': fields.many2one('res.users', 'User', required=True),
+ 'active': fields.boolean('Active'),
+ 'interval_number': fields.integer('Interval Number'),
+ 'interval_type': fields.selection( [('minutes', 'Minutes'),
+ ('hours', 'Hours'), ('days', 'Days'),('weeks', 'Weeks'), ('months', 'Months')], 'Interval Unit'),
+ # number of time the function is called, a negative number
+ # indicates that the function will always be called.
+ 'numbercall': fields.integer('Number of calls'),
+
+ # Repeat missed cronjobs ?
+ 'doall' : fields.boolean('Repeat all missed'),
+
+ 'nextcall' : fields.datetime('Next call date', required=True),
+ 'model': fields.char('Model', size=64),
+ 'function': fields.char('Function', size=64),
+ 'args': fields.text('Arguments'),
+
+ # 0 = Very Urgent, 10 = not urgent
+ 'priority': fields.integer('Priority (0=Very Urgent)')
+ }
+
+ _defaults = {
+ 'nextcall' : lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'priority' : lambda *a: 5,
+ 'user_id' : lambda obj,cr,uid,context: uid,
+ 'interval_number' : lambda *a: 1,
+ 'interval_type' : lambda *a: 'months',
+ 'numbercall' : lambda *a: 1,
+ 'active' : lambda *a: 1,
+ 'doall' : lambda *a: 1
+ }
+
+ def _callback(self, cr, uid, model, func, args):
+ args = (args or []) and eval(args)
+ f = getattr(self.pool.get(model), func)
+ f(cr, uid, *args)
+
+ def _poolJobs(self, db_name, check=False):
+ now = DateTime.now()
+ #FIXME: multidb. Solution: a l'instanciation d'une nouvelle connection bd (ds pooler) fo que j'instancie
+ # un nouveau pooljob avec comme parametre la bd
+ try:
+ cr = pooler.get_db(db_name).cursor()
+ except:
+ return False
+
+ try:
+ cr.execute('select * from ir_cron where numbercall<>0 and active and nextcall<=now() order by priority')
+ for job in cr.dictfetchall():
+ nextcall = DateTime.strptime(job['nextcall'], '%Y-%m-%d %H:%M:%S')
+ numbercall = job['numbercall']
+
+ ok = False
+ while nextcall<now and numbercall:
+ if numbercall > 0:
+ numbercall -= 1
+ if not ok or job['doall']:
+ self._callback(cr, job['user_id'], job['model'], job['function'], job['args'])
+ if numbercall:
+ nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
+ ok = True
+ addsql=''
+ if not numbercall:
+ addsql = ', active=False'
+ cr.execute("update ir_cron set nextcall=%s, numbercall=%d"+addsql+" where id=%d",
+ (nextcall.strftime('%Y-%m-%d %H:%M:%S'), numbercall, job['id']))
+ cr.commit()
+ finally:
+ cr.close()
+ #
+ # Can be improved to do at the min(min(nextcalls), time()+next_wait)
+ # But is this an improvement ?
+ #
+ if not check:
+ self.setAlarm(self._poolJobs, int(time.time())+next_wait, [db_name])
+
+# def __init__(self):
+# super(ir_cron, self).__init__()
+ir_cron()
diff --git a/bin/addons/base/ir/ir_default.py b/bin/addons/base/ir/ir_default.py
new file mode 100644
index 00000000000..fec0829e195
--- /dev/null
+++ b/bin/addons/base/ir/ir_default.py
@@ -0,0 +1,43 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+
+class ir_default(osv.osv):
+ _name = 'ir.default'
+ _columns = {
+ 'field_tbl': fields.char('Model',size=64),
+ 'field_name': fields.char('Model field',size=64),
+ 'value': fields.char('Default Value',size=64),
+ 'uid': fields.many2one('res.users', 'Users'),
+ 'page': fields.char('View',size=64),
+ 'ref_table': fields.char('Table Ref.',size=64),
+ 'ref_id': fields.integer('ID Ref.',size=64),
+ }
+ir_default()
+
diff --git a/bin/addons/base/ir/ir_exports.py b/bin/addons/base/ir/ir_exports.py
new file mode 100644
index 00000000000..5ee1e92afa8
--- /dev/null
+++ b/bin/addons/base/ir/ir_exports.py
@@ -0,0 +1,50 @@
+##############################################################################
+#
+# Copyright (c) 2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# $Id$
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+
+
+class ir_exports(osv.osv):
+ _name = "ir.exports"
+ _columns = {
+ 'name': fields.char('Export name', size=128),
+ 'resource': fields.char('Resource', size=128),
+ 'export_fields': fields.one2many('ir.exports.line', 'export_id',
+ 'Export Id'),
+ }
+ir_exports()
+
+
+class ir_exports_line(osv.osv):
+ _name = 'ir.exports.line'
+ _columns = {
+ 'name': fields.char('Field name', size=64),
+ 'export_id': fields.many2one('ir.exports', 'Exportation', select=True),
+ }
+ir_exports_line()
diff --git a/bin/addons/base/ir/ir_fields_description.py b/bin/addons/base/ir/ir_fields_description.py
new file mode 100644
index 00000000000..9cf89036b51
--- /dev/null
+++ b/bin/addons/base/ir/ir_fields_description.py
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+import ir
+
+
diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py
new file mode 100644
index 00000000000..cbd03803795
--- /dev/null
+++ b/bin/addons/base/ir/ir_model.py
@@ -0,0 +1,273 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+import ir
+import netsvc
+
+import time
+import tools
+import pooler
+
+class ir_model(osv.osv):
+ _name = 'ir.model'
+ _rec_name = 'model'
+ _columns = {
+ 'name': fields.char('Model name', size=64, translate=True),
+ 'model': fields.char('Object name', size=64, required=True),
+ 'info': fields.text('Information'),
+ 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
+ }
+ _defaults = {
+ 'name': lambda *a: 'No Name',
+ }
+ir_model()
+
+class ir_model_fields(osv.osv):
+ _name = 'ir.model.fields'
+ _columns = {
+ 'name': fields.char('Name', size=64),
+ 'model': fields.char('Model Name', size=64, required=True),
+# on pourrait egalement changer ca en many2one, mais le prob c'est qu'alors faut
+# faire une jointure a chaque fois qu'on recherche vu que le client ne connait que le nom
+# de l'objet et pas son id
+ 'relation': fields.char('Model Relation', size=64),
+ 'model_id': fields.many2one('ir.model', 'Model id', required=True, select=True),
+# in fact, this is the field label
+ 'field_description': fields.char('Field Description', size=256),
+ 'ttype': fields.char('Field Type', size=64),
+ 'relate': fields.boolean('Click and Relate'),
+
+ 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
+ 'group_name': fields.char('Group Name', size=128),
+ 'view_load': fields.boolean('View Auto-Load'),
+ }
+ _defaults = {
+ 'relate': lambda *a: 1,
+ 'view_load': lambda *a: 0,
+ 'name': lambda *a: 'No Name',
+ 'field_description': lambda *a: 'No description available',
+ }
+ _order = "id"
+ir_model_fields()
+
+class ir_model_access(osv.osv):
+ _name = 'ir.model.access'
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True),
+ 'model_id': fields.many2one('ir.model', 'Model', required=True),
+ 'group_id': fields.many2one('res.groups', 'Group'),
+ 'perm_read': fields.boolean('Read Access'),
+ 'perm_write': fields.boolean('Write Access'),
+ 'perm_create': fields.boolean('Create Access'),
+ }
+ def check(self, cr, uid, model_name, mode='read'):
+ assert mode in ['read','write','create'], 'Invalid access mode for security'
+
+ # fetch the list of rules for this "permission type" on this model
+ cr.execute('select group_id, perm_'+mode+' from ir_model_access a left join ir_model m on (a.model_id=m.id) where m.model=%s', (model_name,))
+
+ # if no rule is found, grant the access
+ if not cr.rowcount:
+ return True
+
+ # compute the list of groups which have the right we are looking for
+ ids = map(lambda x: str(x[0]), filter(lambda x: x[1], cr.fetchall()))
+ if not ids:
+ raise osv.except_osv('Access denied !', 'You can not %s this resource !' % mode)
+ ids_str = ','.join(ids)
+
+ # check if the user is part of one of those groups
+ cr.execute('select count(*) from res_groups_users_rel where uid=%d and gid in ('+ids_str+')', (uid,))
+ if cr.fetchone()[0]:
+ return True
+ raise osv.except_osv('Access denied !', 'You can not %s this resource !' % mode)
+ check = tools.cache()(check)
+
+ #
+ # Methods to clean the cache on the Check Method.
+ #
+ def write(self, cr, uid, *args, **argv):
+ res = super(ir_model_access, self).write(cr, uid, *args, **argv)
+ self.check()
+ return res
+ def create(self, cr, uid, *args, **argv):
+ res = super(ir_model_access, self).create(cr, uid, *args, **argv)
+ self.check()
+ return res
+ def unlink(self, cr, uid, *args, **argv):
+ res = super(ir_model_access, self).unlink(cr, uid, *args, **argv)
+ self.check()
+ return res
+ir_model_access()
+
+class ir_model_data(osv.osv):
+ _name = 'ir.model.data'
+ _columns = {
+ 'name': fields.char('XML Identifier', required=True, size=64),
+ 'model': fields.char('Model', required=True, size=64),
+ 'module': fields.char('Module', required=True, size=64),
+ 'res_id': fields.integer('Resource ID'),
+ 'noupdate': fields.boolean('Non Updatable'),
+ 'date_update': fields.datetime('Update Date'),
+ 'date_init': fields.datetime('Init Date')
+ }
+ _defaults = {
+ 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'noupdate': lambda *a: False
+ }
+
+ def __init__(self, pool):
+ osv.osv.__init__(self, pool)
+ self.loads = {}
+ self.doinit = True
+ self.unlink_mark = {}
+
+ def _get_id(self,cr, uid, module, xml_id):
+ ids = self.search(cr, uid, [('module','=',module),('name','=', xml_id)])
+ assert len(ids)==1, '%d reference(s) to %s. You should have only one !' % (len(ids),xml_id)
+ return ids[0]
+
+ def _update_dummy(self,cr, uid, model, module, xml_id=False, store=True):
+ if not xml_id:
+ return False
+ try:
+ id = self.read(cr, uid, [self._get_id(cr, uid, module, xml_id)], ['res_id'])[0]['res_id']
+ self.loads[(module,xml_id)] = (model,id)
+ except:
+ id = False
+ return id
+
+ def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False):
+ warning = True
+ if xml_id and ('.' in xml_id):
+ assert len(xml_id.split('.'))==2, '"%s" contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id' % (xml_id)
+ warning = False
+ module, xml_id = xml_id.split('.')
+ if (not xml_id) and (not self.doinit):
+ return False
+ action_id = False
+ if xml_id:
+ cr.execute('select id,res_id from ir_model_data where module=%s and name=%s', (module,xml_id))
+ results = cr.fetchall()
+ for action_id2,res_id2 in results:
+ cr.execute('select id from '+self.pool.get(model)._table+' where id=%d', (res_id2,))
+ result3 = cr.fetchone()
+ if not result3:
+ cr.execute('delete from ir_model_data where id=%d', (action_id2,))
+ else:
+ res_id,action_id = res_id2,action_id2
+
+ if action_id and res_id:
+ self.pool.get(model).write(cr, uid, [res_id], values)
+ self.write(cr, uid, [action_id], {'date_update': time.strftime('%Y-%m-%d %H:%M:%S')})
+ elif res_id:
+ self.pool.get(model).write(cr, uid, [res_id], values)
+ if xml_id:
+ self.create(cr, uid, {'name':xml_id, 'model':model, 'module':module, 'res_id':res_id, 'noupdate':noupdate})
+ else:
+ if mode=='init' or (mode=='update' and xml_id):
+ res_id = self.pool.get(model).create(cr, uid, values)
+ if xml_id:
+ self.create(cr, uid, {'name':xml_id, 'model':model, 'module':module, 'res_id':res_id, 'noupdate':noupdate})
+ if xml_id:
+ if warning:
+ assert (module,xml_id) not in self.loads, "id '%s' is already defined in module '%s' !" % (xml_id, module)
+ if res_id:
+ self.loads[(module,xml_id)] = (model, res_id)
+ return res_id
+
+ def _unlink(self, cr, uid, model, ids, direct=False):
+ #self.pool.get(model).unlink(cr, uid, ids)
+ for id in ids:
+ self.unlink_mark[(model, id)]=False
+ cr.execute('delete from ir_model_data where res_id=%d and model=\'%s\'', (id,model))
+ return True
+
+ def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None):
+ obj = self.pool.get('ir.values')
+ if type(models[0])==type([]) or type(models[0])==type(()):
+ model,res_id = models[0]
+ else:
+ res_id=None
+ model = models[0]
+
+ if res_id:
+ where = ' and res_id=%d' % (res_id,)
+ else:
+ where = ' and (res_id is null)'
+
+ if key2:
+ where += ' and key2=\'%s\'' % (key2,)
+ else:
+ where += ' and (key2 is null)'
+
+ cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
+ res = cr.fetchone()
+ if not res:
+ res = ir.ir_set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
+ return True
+
+ def _process_end(self, cr, uid, modules):
+ if not modules:
+ return True
+ module_str = ["'%s'" % m for m in modules]
+ cr.execute('select id,name,model,res_id,module from ir_model_data where module in ('+','.join(module_str)+') and not noupdate')
+ wkf_todo = []
+ for (id, name, model, res_id,module) in cr.fetchall():
+ if (module,name) not in self.loads:
+ self.unlink_mark[(model,res_id)] = id
+ if model=='workflow.activity':
+ cr.execute('select res_type,res_id from wkf_instance where id in (select inst_id from wkf_workitem where act_id=%d)', (res_id,))
+ wkf_todo.extend(cr.fetchall())
+ cr.execute("update wkf_transition set condition='True', role_id=NULL, signal=NULL,act_to=act_from,act_from=%d where act_to=%d", (res_id,res_id))
+ cr.execute("delete from wkf_transition where act_to=%d", (res_id,))
+
+ for model,id in wkf_todo:
+ wf_service = netsvc.LocalService("workflow")
+ wf_service.trg_write(uid, model, id, cr)
+
+ cr.commit()
+ for (model,id) in self.unlink_mark.keys():
+ if self.pool.get(model):
+ print 'Deleting %s@%s' % (id, model),
+ try:
+ self.pool.get(model).unlink(cr, uid, [id])
+ if self.unlink_mark[(model,id)]:
+ self.unlink(cr, uid, [self.unlink_mark[(model,id)]])
+ cr.commit()
+ print '... ok'
+ except:
+ print '... ko'
+ print '-'*60
+ print 'WARNING: Could not delete id', id, 'of model', model
+ print '\tThere should be some relation that points to this resource'
+ print '\tYou should manually fix this and restart --update=module'
+ return True
+ir_model_data()
+
diff --git a/bin/addons/base/ir/ir_report_custom.py b/bin/addons/base/ir/ir_report_custom.py
new file mode 100644
index 00000000000..52cc603d6e3
--- /dev/null
+++ b/bin/addons/base/ir/ir_report_custom.py
@@ -0,0 +1,206 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+from osv.orm import browse_null
+import ir
+import report.custom
+
+class report_custom(osv.osv):
+ _name = 'ir.report.custom'
+ _columns = {
+ 'name': fields.char('Report Name', size=64, required=True, translate=True),
+ 'model_id': fields.many2one('ir.model','Model', required=True, change_default=True),
+ 'type': fields.selection([('table','Tabular'),('pie','Pie Chart'),('bar','Bar Chart'),('line','Line Plot')], "Report Type", size=64, required='True'),
+ 'title': fields.char("Report title", size=64, required='True', translate=True),
+ 'print_format': fields.selection((('A4','a4'),('A5','a5')), 'Print format', required=True),
+ 'print_orientation': fields.selection((('landscape','Landscape'),('portrait','Portrait')), 'Print orientation', required=True, size=16),
+ 'repeat_header': fields.boolean('Repeat Header'),
+ 'footer': fields.char('Report Footer', size=64, required=True),
+ 'sortby': fields.char('Sorted By', size=64),
+ 'fields_child0': fields.one2many('ir.report.custom.fields', 'report_id','Fields', required=True),
+ 'field_parent': fields.many2one('ir.model.fields','Child Field'),
+ 'state': fields.selection([('unsubscribed','Unsubscribed'),('subscribed','Subscribed')], 'State', size=64),
+ 'frequency': fields.selection([('Y','Yearly'),('M','Monthly'),('D','Daily')], 'Frequency', size=64),
+ 'limitt': fields.char('Limit', size=9),
+ 'menu_id': fields.many2one('ir.ui.menu', 'Menu')
+ }
+ _defaults = {
+ 'print_format': lambda *a: 'A4',
+ 'print_orientation': lambda *a: 'portrait',
+ 'state': lambda *a: 'unsubscribed',
+ 'type': lambda *a: 'table',
+ 'footer': lambda *a: 'Generated by Tiny ERP'
+ }
+
+ def onchange_model_id(self, cr, uid, ids, model_id):
+ if not(model_id):
+ return {}
+ return {'domain': {'field_parent': [('model_id','=',model_id)]}}
+
+ def unsubscribe(self, cr, uid, ids, context={}):
+#TODO: should delete the ir.actions.report.custom for these reports and do an ir_del
+ self.write(cr, uid, ids, {'state':'unsubscribed'})
+ return True
+
+ def subscribe(self, cr, uid, ids, context={}):
+ for report in self.browse(cr, uid, ids):
+ report.fields_child0.sort(lambda x,y : x.sequence - y.sequence)
+
+ # required on field0 does not seem to work( cause we use o2m_l ?)
+ if not report.fields_child0:
+ raise osv.except_osv('Invalid operation :', 'Enter at least one field !')
+
+ if report.type in ['pie', 'bar', 'line'] and report.field_parent:
+ raise osv.except_osv('Invalid operation :', 'Tree can only be used in tabular reports')
+
+ # Otherwise it won't build a good tree. See level.pop in custom.py.
+ if report.type == 'table' and report.field_parent and report.fields_child0 and not report.fields_child0[0].groupby:
+ raise osv.except_osv('Invalid operation :', 'When creating tree (field child) report, data must be group by the first field')
+
+ if report.type == 'pie':
+ if len(report.fields_child0) != 2:
+ raise osv.except_osv('Invalid operation :', 'Pie charts need exactly two fields')
+ else:
+ c_f = {}
+ for i in range(2):
+ c_f[i] = []
+ tmp = report.fields_child0[i]
+ for j in range(3):
+ c_f[i].append((not isinstance(eval('tmp.field_child'+str(j)), browse_null) and eval('tmp.field_child'+str(j)+'.ttype')) or None)
+ if not reduce(lambda x,y : x or y, map(lambda x: x in ['integer', 'float'], c_f[1])):
+ raise osv.except_osv('Invalid operation : ','Second field should be figures')
+
+ if report.type == 'bar':
+ if len(report.fields_child0) < 2:
+ raise osv.except_osv('Invalid operation : ','Bar charts need at least two fields')
+ else:
+ c_f = {}
+ for i in range(len(report.fields_child0)):
+ c_f[i] = []
+ tmp = report.fields_child0[i]
+ for j in range(3):
+ c_f[i].append((not isinstance(eval('tmp.field_child'+str(j)), browse_null) and eval('tmp.field_child'+str(j)+'.ttype')) or None)
+
+ if i == 0:
+ pass
+ else:
+ if not reduce(lambda x,y : x or y, map(lambda x: x in ['integer', 'float'], c_f[i])):
+ raise osv.except_osv('Invalid operation : ','Field %d should be a figure' %(i,))
+
+ if report.state=='subscribed':
+ continue
+
+ name = report.name
+ model = report.model_id.model
+
+ action_def = {'report_id':report.id, 'type':'ir.actions.report.custom', 'model':model, 'name':name}
+ id = self.pool.get('ir.actions.report.custom').create(cr, uid, action_def)
+ m_id = report.menu_id.id
+ action = "ir.actions.report.custom,%d" % (id,)
+ if not report.menu_id:
+ ir.ir_set(cr, uid, 'action', 'client_print_multi', name, [(model, False)], action, False, True)
+ else:
+ ir.ir_set(cr, uid, 'action', 'tree_but_open', 'Menuitem', [('ir.ui.menu', int(m_id))], action, False, True)
+
+ self.write(cr, uid, [report.id], {'state':'subscribed'}, context)
+ return True
+report_custom()
+
+
+class report_custom_fields(osv.osv):
+ _name = 'ir.report.custom.fields'
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True),
+ 'report_id': fields.many2one('ir.report.custom', 'Report Ref', select=True),
+ 'field_child0': fields.many2one('ir.model.fields', 'field child0', required=True),
+ 'fc0_operande': fields.many2one('ir.model.fields', 'Constraint'),
+ 'fc0_condition': fields.char('Condition', size=64),
+ 'fc0_op': fields.selection((('>','>'),('<','<'),('==','='),('in','in'),('gety,==','(year)=')), 'Relation'),
+ 'field_child1': fields.many2one('ir.model.fields', 'field child1'),
+ 'fc1_operande': fields.many2one('ir.model.fields', 'Constraint'),
+ 'fc1_condition': fields.char('condition', size=64),
+ 'fc1_op': fields.selection((('>','>'),('<','<'),('==','='),('in','in'),('gety,==','(year)=')), 'Relation'),
+ 'field_child2': fields.many2one('ir.model.fields', 'field child2'),
+ 'fc2_operande': fields.many2one('ir.model.fields', 'Constraint'),
+ 'fc2_condition': fields.char('condition', size=64),
+ 'fc2_op': fields.selection((('>','>'),('<','<'),('==','='),('in','in'),('gety,==','(year)=')), 'Relation'),
+ 'field_child3': fields.many2one('ir.model.fields', 'field child3'),
+ 'fc3_operande': fields.many2one('ir.model.fields', 'Constraint'),
+ 'fc3_condition': fields.char('condition', size=64),
+ 'fc3_op': fields.selection((('>','>'),('<','<'),('==','='),('in','in'),('gety,==','(year)=')), 'Relation'),
+ 'alignment': fields.selection((('left','left'),('right','right'),('center','center')), 'Alignment', required=True),
+ 'sequence': fields.integer('Sequence', required=True),
+ 'width': fields.integer('Fixed Width'),
+ 'operation': fields.selection((('none', 'None'),('calc_sum','Calculate Sum'),('calc_avg','Calculate Average'),('calc_count','Calculate Count'),('calc_max', 'Get Max'))),
+ 'groupby' : fields.boolean('Group by'),
+ 'bgcolor': fields.char('Background Color', size=64),
+ 'fontcolor': fields.char('Font color', size=64),
+ 'cumulate': fields.boolean('Cumulate')
+ }
+ _defaults = {
+ 'alignment': lambda *a: 'left',
+ 'bgcolor': lambda *a: 'white',
+ 'fontcolor': lambda *a: 'black',
+ 'operation': lambda *a: 'none',
+ }
+ _order = "sequence"
+
+ def onchange_any_field_child(self, cr, uid, ids, field_id, level):
+ if not(field_id):
+ return {}
+ next_level_field_name = 'field_child%d' % (level+1)
+ next_level_operande = 'fc%d_operande' % (level+1)
+ field = self.pool.get('ir.model.fields').browse(cr, uid, [field_id])[0]
+ res = self.pool.get(field.model).fields_get(cr, uid, field.name)
+ if res[field.name].has_key('relation'):
+ cr.execute('select id from ir_model where model=%s', (res[field.name]['relation'],))
+ (id,) = cr.fetchone() or (False,)
+ if id:
+ return {
+ 'domain': {
+ next_level_field_name: [('model_id', '=', id)],
+ next_level_operande: [('model_id', '=', id)]
+ },
+ 'required': {
+ next_level_field_name: True
+ }
+ }
+ else:
+ print "Warning: using a relation field which uses an unknown object"
+ return {'required': {next_level_field_name: True}}
+ else:
+ return {'domain': {next_level_field_name: []}}
+
+ def get_field_child_onchange_method(level):
+ return lambda self, cr, uid, ids, field_id: self.onchange_any_field_child(cr, uid, ids, field_id, level)
+
+ onchange_field_child0 = get_field_child_onchange_method(0)
+ onchange_field_child1 = get_field_child_onchange_method(1)
+ onchange_field_child2 = get_field_child_onchange_method(2)
+report_custom_fields()
+
diff --git a/bin/addons/base/ir/ir_sequence.py b/bin/addons/base/ir/ir_sequence.py
new file mode 100644
index 00000000000..7b8ef925820
--- /dev/null
+++ b/bin/addons/base/ir/ir_sequence.py
@@ -0,0 +1,93 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import threading
+import time
+from osv import fields,osv
+
+class ir_sequence_type(osv.osv):
+ _name = 'ir.sequence.type'
+ _columns = {
+ 'name': fields.char('Sequence Name',size=64, required=True),
+ 'code': fields.char('Sequence Code',size=32, required=True),
+ }
+ir_sequence_type()
+
+def _code_get(self, cr, uid, context={}):
+ cr.execute('select code, name from ir_sequence_type')
+ return cr.fetchall()
+
+class ir_sequence(osv.osv):
+ _name = 'ir.sequence'
+ _columns = {
+ 'name': fields.char('Sequence Name',size=64, required=True),
+ 'code': fields.selection(_code_get, 'Sequence Code',size=64, required=True),
+ 'active': fields.boolean('Active'),
+ 'prefix': fields.char('Prefix',size=64),
+ 'suffix': fields.char('Suffix',size=64),
+ 'number_next': fields.integer('Next Number', required=True),
+ 'number_increment': fields.integer('Increment Number', required=True),
+ 'padding' : fields.integer('Number padding', required=True),
+ }
+ _defaults = {
+ 'active': lambda *a: True,
+ 'number_increment': lambda *a: 1,
+ 'number_next': lambda *a: 1,
+ 'padding' : lambda *a : 0,
+ }
+
+ sequence_semaphore = threading.Semaphore()
+
+ def _process(self, s):
+ return (s or '') % {'year':time.strftime('%Y'), 'month': time.strftime('%m'), 'day':time.strftime('%d')}
+
+ def get_id(self, cr, uid, sequence_id, test='id=%d'):
+ self.sequence_semaphore.acquire()
+ cr.execute('select id,number_next,number_increment,prefix,suffix,padding from ir_sequence where '+test+' and active=True', (sequence_id,))
+ res = cr.dictfetchone()
+ if res:
+ cr.execute('update ir_sequence set number_next=number_next+number_increment where id=%d and active=True', (res['id'],))
+ self.sequence_semaphore.release()
+ if res['number_next']:
+ return self._process(res['prefix']) + '%%0%sd' % res['padding'] % res['number_next'] + self._process(res['suffix'])
+ else:
+ return self._process(res['prefix']) + self._process(res['suffix'])
+ else:
+ self.sequence_semaphore.release()
+ return False
+
+ def get(self, cr, uid, code):
+ return self.get_id(cr, uid, code, test='code=%s')
+
+ def set(self, cr, uid, code, next_number):
+ self.sequence_semaphore.acquire()
+ cr.execute('update ir_sequence set number_next=%d where code=%s and active=True', (next_number, code,))
+ self.sequence_semaphore.release()
+ return True
+ir_sequence()
+
diff --git a/bin/addons/base/ir/ir_translation.py b/bin/addons/base/ir/ir_translation.py
new file mode 100644
index 00000000000..d6485a6aec0
--- /dev/null
+++ b/bin/addons/base/ir/ir_translation.py
@@ -0,0 +1,88 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields, osv
+from osv.osv import Cacheable
+
+class ir_translation(osv.osv, Cacheable):
+ _name = "ir.translation"
+ _log_access = False
+ _columns = {
+ 'name': fields.char('Field Name', size=128, required=True),
+ 'res_id': fields.integer('Resource ID'),
+ 'lang': fields.char('Language', size=5),
+ 'type': fields.char('Type', size=16),
+ 'src': fields.text('Source'),
+ 'value': fields.text('Translation Value'),
+ }
+ _sql = """
+ create index ir_translation_ltn on ir_translation (lang,type,name);
+ create index ir_translation_res_id on ir_translation (res_id);
+ """
+
+ def _get_ids(self, cr, uid, name, tt, lang, ids):
+ translations, to_fetch = {}, []
+ for id in ids:
+ trans = self.get((lang, name, id))
+ if trans:
+ translations[id] = trans
+ else:
+ to_fetch.append(id)
+ if to_fetch:
+ cr.execute('select res_id,value from ir_translation where lang=%s and type=%s and name=%s and res_id in ('+','.join(map(str, to_fetch))+')', (lang,tt,name))
+ for res_id, value in cr.fetchall():
+ self.add((lang, tt, name, res_id), value)
+ translations[res_id] = value
+ return translations
+
+ def _set_ids(self, cr, uid, name, tt, lang, ids, value):
+ cr.execute('delete from ir_translation where lang=%s and type=%s and name=%s and res_id in ('+','.join(map(str,ids))+')', (lang,tt,name))
+ for id in ids:
+ self.create(cr, uid, {'lang':lang, 'type':tt, 'name':name, 'res_id':id, 'value':value})
+ return len(ids)
+
+ def _get_source(self, cr, uid, name, tt, lang, source=None):
+ trans = self.get((lang, tt, name, source))
+ if trans:
+ return trans
+
+ if source:
+ source = source.strip().replace('\n',' ')
+ if isinstance(source, unicode):
+ source = source.encode('utf8')
+ cr.execute('select value from ir_translation where lang=%s and type=%s and name=%s and src=%s', (lang, tt, str(name), source))
+ else:
+ cr.execute('select value from ir_translation where lang=%s and type=%s and name=%s', (lang, tt, str(name)))
+ res = cr.fetchone()
+ if res:
+ self.add((lang, tt, name, source), res[0])
+ return res[0]
+ else:
+ self.add((lang, tt, name, source), False)
+ return False
+ir_translation()
diff --git a/bin/addons/base/ir/ir_ui_menu.py b/bin/addons/base/ir/ir_ui_menu.py
new file mode 100644
index 00000000000..8aa96e2fa5e
--- /dev/null
+++ b/bin/addons/base/ir/ir_ui_menu.py
@@ -0,0 +1,123 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# $Id: ir_ui_menu.py 1005 2005-07-25 08:41:42Z nicoe $
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields, osv
+from osv.orm import browse_null, browse_record
+
+def one_in(setA, setB):
+ """Check the presence of an element of setA in setB
+ """
+ for x in setA:
+ if x in setB:
+ return True
+ return False
+
+icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
+'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
+'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
+'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
+'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
+'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
+'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
+'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
+'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
+'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
+'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
+'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
+'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
+'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
+'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
+'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
+'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
+'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
+'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
+'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
+'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
+'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
+'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
+'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase', 'terp-sale', 'terp-tools',
+'terp-administration', 'terp-hr', 'terp-partner', 'terp-project', 'terp-report', 'terp-stock'
+])
+
+
+class ir_ui_menu(osv.osv):
+ _name = 'ir.ui.menu'
+ def search(self, cr, uid, args, offset=0, limit=2000, order=None):
+ ids = osv.orm.orm.search(self, cr, uid, args, offset, limit, order)
+ user_groups = self.pool.get('res.users').read(cr, uid, [uid])[0]['groups_id']
+ result = []
+ for menu in self.browse(cr, uid, ids):
+ if not len(menu.groups_id):
+ result.append(menu.id)
+ continue
+ for g in menu.groups_id:
+ if g.id in user_groups:
+ result.append(menu.id)
+ break
+ return result
+
+ def _get_full_name(self, cr, uid, ids, name, args, context):
+ res = {}
+ for m in self.browse(cr, uid, ids):
+ res[m.id] = self._get_one_full_name(m)
+ return res
+
+ def _get_one_full_name(self, menu, level=6):
+ if level<=0:
+ return '...'
+ if menu.parent_id:
+ parent_path = self._get_one_full_name(menu.parent_id, level-1) + "/"
+ else:
+ parent_path = ''
+ return parent_path + menu.name
+
+ def copy(self, cr, uid, id, default=None, context={}):
+ res = super(ir_ui_menu, self).copy(cr, uid, id, context=context)
+ ids = self.pool.get('ir.values').search(cr, uid, [('model','=','ir.ui.menu'),('res_id','=',id)])
+ for iv in self.pool.get('ir.values').browse(cr, uid, ids):
+ new_id = self.pool.get('ir.values').copy(cr, uid, iv.id, default={'res_id':res}, context=context)
+ return res
+
+ _columns = {
+ 'name': fields.char('Menu', size=64, required=True, translate=True),
+ 'sequence': fields.integer('Sequence'),
+ 'child_id' : fields.one2many('ir.ui.menu', 'parent_id','Child ids'),
+ 'parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', select=True),
+ 'groups_id': fields.many2many('res.groups', 'ir_ui_menu_group_rel', 'menu_id', 'gid', 'Groups'),
+ 'complete_name': fields.function(_get_full_name, method=True, string='Complete Name', type='char', size=128),
+ 'icon': fields.selection(lambda *a: icons, 'Icon', size=64)
+ }
+ _defaults = {
+ 'icon' : lambda *a: 'STOCK_OPEN',
+ 'sequence' : lambda *a: 10
+ }
+ _order = "sequence,id"
+ir_ui_menu()
+
+
diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py
new file mode 100644
index 00000000000..bcef65b1a98
--- /dev/null
+++ b/bin/addons/base/ir/ir_ui_view.py
@@ -0,0 +1,76 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+from xml import dom
+
+def _check_xml(self, cr, uid, ids):
+ try:
+ cr.execute('select arch from ir_ui_view where id in ('+','.join(map(str,ids))+')')
+ for row in cr.fetchall():
+ doc = dom.minidom.parseString(row[0])
+ return True
+ except Exception, e:
+ return False
+
+class view(osv.osv):
+ _name = 'ir.ui.view'
+ _columns = {
+ 'name': fields.char('View Name',size=64, required=True),
+ 'model': fields.char('Model', size=64, required=True),
+ 'priority': fields.integer('Priority', required=True),
+ 'type': fields.selection((('tree','Tree'),('form','Form')), 'View Type', required=True),
+ 'arch': fields.text('View Architecture', required=True),
+ 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View'),
+ 'field_parent': fields.char('Childs Field',size=64)
+ }
+ _defaults = {
+ 'arch': lambda *a: '<?xml version="1.0"?>\n<tree title="Unknwown">\n\t<field name="name"/>\n</tree>',
+ 'priority': lambda *a: 16
+ }
+ _order = "priority"
+ _constraints = [
+ (_check_xml, 'Invalid XML for View Architecture!', ['arch'])
+ ]
+
+view()
+
+class view_sc(osv.osv):
+ _name = 'ir.ui.view_sc'
+ _columns = {
+ 'name': fields.char('Shortcut Name', size=64, required=True),
+ 'res_id': fields.integer('Resource Ref.', required=True),
+ 'sequence': fields.integer('Sequence'),
+ 'user_id': fields.many2one('res.users', 'User Ref.', required=True, ondelete='cascade'),
+ 'resource': fields.char('Resource Name', size=64, required=True)
+ }
+ _order = 'sequence'
+ _defaults = {
+ 'resource': lambda *a: 'ir.ui.menu',
+ }
+view_sc()
diff --git a/bin/addons/base/ir/ir_values.py b/bin/addons/base/ir/ir_values.py
new file mode 100644
index 00000000000..68556eab755
--- /dev/null
+++ b/bin/addons/base/ir/ir_values.py
@@ -0,0 +1,153 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import osv,fields
+import pickle
+
+class ir_values(osv.osv):
+ _name = 'ir.values'
+ _columns = {
+ 'name': fields.char('Name', size=128),
+ 'model': fields.char('Name', size=128),
+ 'value': fields.text('Value'),
+ 'object': fields.boolean('Is Object'),
+ 'key': fields.char('Name', size=128),
+ 'key2': fields.char('Value', size=256),
+ 'meta': fields.text('Meta Datas'),
+ 'res_id': fields.integer('Resource ID'),
+ 'user_id': fields.many2one('res.users', 'User', ondelete='cascade')
+ }
+ _defaults = {
+ 'key': lambda *a: 'action',
+ 'key2': lambda *a: 'tree_but_open',
+ }
+ def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False):
+ if not isobject:
+ value = pickle.dumps(value)
+ if meta:
+ meta = pickle.dumps(meta)
+ ids_res = []
+ for model in models:
+ if type(model)==type([]) or type(model)==type(()):
+ model,res_id = model
+ else:
+ res_id=False
+ if replace:
+ ids = self.search(cr, uid, [('key','=',key),('key2','=',key2),('name','=',name),('model','=',model),('res_id','=',res_id)])
+ self.unlink(cr, uid, ids)
+ vals = {
+ 'name': name,
+ 'value': value,
+ 'model': model,
+ 'object': isobject,
+ 'key': key,
+ 'key2': key2 and key2[:200],
+ 'meta': meta,
+ 'user_id': preserve_user and uid,
+ }
+ if res_id:
+ vals['res_id']= res_id
+ ids_res.append(self.create(cr, uid, vals))
+ return ids_res
+
+ #
+ # Improve to add user_id check
+ #
+ def get(self, cr, uid, key, key2, models, meta=False, context={}, res_id_req=False, without_user=True, key2_req=True):
+ result = []
+ for m in models:
+ if type(m)==type([]) or type(m)==type(()):
+ m,res_id = m
+ else:
+ res_id=False
+
+ where1 = ['key=%s','model=%s']
+ where2 = [key,m]
+ where_opt = []
+ if key2:
+ where1.append('key2=%s')
+ where2.append(key2[:200])
+ else:
+ dest = where1
+ if not key2_req or meta:
+ dest=where_opt
+ dest.append('key2 is null')
+
+ if res_id_req and (models[-1][0]==m):
+ if res_id:
+ where1.append('res_id=%d' % (res_id,))
+ else:
+ where1.append('(res_id is NULL)')
+ else:
+ if res_id:
+ if (models[-1][0]==m):
+ where1.append('(res_id=%d or (res_id is null))' % (res_id,))
+ where_opt.append('res_id=%d' % (res_id,))
+ else:
+ where1.append('res_id=%d' % (res_id,))
+
+ if not without_user:
+ where_opt.append('user_id=%d' % (uid,))
+
+ result = []
+ ok = True
+ while ok and len(result)==0:
+ cr.execute('select id from ir_values where '+' and '.join(where1+where_opt), where2)
+ result = [x[0] for x in cr.fetchall()]
+ if len(where_opt):
+ where_opt.pop()
+ else:
+ ok = False
+
+ if result:
+ break
+
+ if not result:
+ return []
+ cr.execute('select id,name,value,object,meta from ir_values where id in ('+','.join(map(str,result))+')')
+ result = cr.fetchall()
+
+ def _result_get(x):
+ if x[3]:
+ model,id = x[2].split(',')
+ datas = self.pool.get(model).read(cr, uid, [int(id)], False, context)
+ if not len(datas):
+ #ir_del(cr, uid, x[0])
+ return False
+ datas = datas[0]
+ else:
+ datas = pickle.loads(x[2])
+ if meta:
+ meta2 = pickle.loads(x[4])
+ return (x[0],x[1],datas,meta2)
+ return (x[0],x[1],datas)
+ res = filter(bool, map(lambda x: _result_get(x), list(result)))
+ return res
+ir_values()
+
+
diff --git a/bin/addons/base/ir/workflow/__init__.py b/bin/addons/base/ir/workflow/__init__.py
new file mode 100644
index 00000000000..adfcc0d2ac5
--- /dev/null
+++ b/bin/addons/base/ir/workflow/__init__.py
@@ -0,0 +1,2 @@
+import workflow
+import print_instance
diff --git a/bin/addons/base/ir/workflow/instance_print.py b/bin/addons/base/ir/workflow/instance_print.py
new file mode 100644
index 00000000000..aff6e7f22d1
--- /dev/null
+++ b/bin/addons/base/ir/workflow/instance_print.py
@@ -0,0 +1,88 @@
+import psycopg
+import pydot
+
+wkf_id = 231
+
+db = psycopg.connect('dbname=terp', serialize=0)
+cr=db.cursor()
+
+def graph_get(cr, graph, wkf_id, nested=True, workitem={}):
+ cr.execute('select * from wkf_activity where wkf_id=%d', (wkf_id,))
+ nodes = cr.dictfetchall()
+ activities = {}
+ actfrom = {}
+ actto = {}
+ for n in nodes:
+ activities[n['id']] = n
+ if n['subflow_id'] and nested:
+ cr.execute('select * from wkf where id=%d', (n['subflow_id'],))
+ wkfinfo = cr.dictfetchone()
+ graph2 = pydot.Cluster('subflow'+str(n['subflow_id']), fontsize=10, label = "Subflow: "+n['name']+'\\nOSV: '+wkfinfo['osv'])
+ (s1,s2) = graph_get(cr, graph2, n['subflow_id'], nested,workitem)
+ graph.add_subgraph(graph2)
+ actfrom[n['id']] = s2
+ actto[n['id']] = s1
+ else:
+ args = {}
+ if n['flow_start'] or n['flow_stop']:
+ args['style']='filled'
+ args['color']='lightgrey'
+ args['label']=n['name']
+ if n['action']:
+ args['label']+='\\n'+n['action']
+ if n['subflow_id']:
+ args['shape'] = 'box'
+ if n['id'] in workitem:
+ args['label']+='\\nx '+str(workitem[n['id']])
+ args['color'] = "red"
+ graph.add_node(pydot.Node(n['id'], **args))
+ actfrom[n['id']] = n['id']
+ actto[n['id']] = n['id']
+ cr.execute('select * from wkf_transition where act_from in ('+','.join(map(lambda x: str(x['id']),nodes))+')')
+ transitions = cr.dictfetchall()
+ for t in transitions:
+ args = {}
+ args['label'] = str(t['condition'])
+ if t['signal']:
+ args['label'] += '\\n'+str(t['signal'])
+ args['style'] = 'bold'
+
+ if activities[t['act_from']]['split_mode']=='AND':
+ args['arrowtail']='box'
+ elif str(activities[t['act_from']]['split_mode'])=='OR ':
+ args['arrowtail']='inv'
+
+ if activities[t['act_to']]['join_mode']=='AND':
+ args['arrowhead']='crow'
+
+ graph.add_edge(pydot.Edge(actfrom[t['act_from']],actto[t['act_to']], fontsize=8, **args))
+ nodes = cr.dictfetchall()
+ cr.execute('select id from wkf_activity where flow_start=True limit 1')
+ start = cr.fetchone()[0]
+ cr.execute('select id from wkf_activity where flow_stop=True limit 1')
+ stop = cr.fetchone()[0]
+ return (start,stop)
+
+def graph_instance_get(cr, graph, inst_id, nested=False):
+ workitems = {}
+ cr.execute('select * from wkf_instance where id=%d', (inst_id,))
+ inst = cr.dictfetchone()
+
+ def workitem_get(instance):
+ cr.execute('select act_id,count(*) from wkf_workitem where inst_id=%d group by act_id', (instance,))
+ workitems = dict(cr.fetchall())
+
+ cr.execute('select subflow_id from wkf_workitem where inst_id=%d', (instance,))
+ for (subflow_id,) in cr.fetchall():
+ workitems.update(workitem_get(subflow_id))
+ return workitems
+
+ graph_get(cr, graph, inst['wkf_id'], nested, workitem_get(inst_id))
+
+
+cr.execute('select * from wkf where id=%d', (wkf_id,))
+wkfinfo = cr.dictfetchone()
+graph = pydot.Dot(fontsize = 16, label = "\\n\\nWorkflow: %s\\n OSV: %s"% (wkfinfo['name'],wkfinfo['osv']))
+graph_instance_get(cr, graph, 1155, True)
+graph.write_ps('/tmp/a.ps', prog='dot')
+
diff --git a/bin/addons/base/ir/workflow/print_instance.py b/bin/addons/base/ir/workflow/print_instance.py
new file mode 100644
index 00000000000..6aaef8abdd3
--- /dev/null
+++ b/bin/addons/base/ir/workflow/print_instance.py
@@ -0,0 +1,172 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import time, os
+
+import netsvc
+import report,pooler,tools
+
+
+def graph_get(cr, graph, wkf_id, nested=False, workitem={}):
+ import pydot
+ cr.execute('select * from wkf_activity where wkf_id=%d', (wkf_id,))
+ nodes = cr.dictfetchall()
+ activities = {}
+ actfrom = {}
+ actto = {}
+ for n in nodes:
+ activities[n['id']] = n
+ if n['subflow_id'] and nested:
+ cr.execute('select * from wkf where id=%d', (n['subflow_id'],))
+ wkfinfo = cr.dictfetchone()
+ graph2 = pydot.Cluster('subflow'+str(n['subflow_id']), fontsize=10, label = "Subflow: "+n['name']+'\\nOSV: '+wkfinfo['osv'])
+ (s1,s2) = graph_get(cr, graph2, n['subflow_id'], nested,workitem)
+ graph.add_subgraph(graph2)
+ actfrom[n['id']] = s2
+ actto[n['id']] = s1
+ else:
+ args = {}
+ if n['flow_start'] or n['flow_stop']:
+ args['style']='filled'
+ args['color']='lightgrey'
+ args['label']=n['name']
+ if n['subflow_id']:
+ args['shape'] = 'box'
+ if n['id'] in workitem:
+ args['label']+='\\nx '+str(workitem[n['id']])
+ args['color'] = "red"
+ graph.add_node(pydot.Node(n['id'], **args))
+ actfrom[n['id']] = (n['id'],{})
+ actto[n['id']] = (n['id'],{})
+ cr.execute('select * from wkf_transition where act_from in ('+','.join(map(lambda x: str(x['id']),nodes))+')')
+ transitions = cr.dictfetchall()
+ for t in transitions:
+ args = {}
+ args['label'] = str(t['condition'])
+ if t['signal']:
+ args['label'] += '\\n'+str(t['signal'])
+ args['style'] = 'bold'
+
+ if activities[t['act_from']]['split_mode']=='AND':
+ args['arrowtail']='box'
+ elif str(activities[t['act_from']]['split_mode'])=='OR ':
+ args['arrowtail']='inv'
+
+ if activities[t['act_to']]['join_mode']=='AND':
+ args['arrowhead']='crow'
+
+ activity_from = actfrom[t['act_from']][1].get(t['signal'], actfrom[t['act_from']][0])
+ activity_to = actto[t['act_to']][1].get(t['signal'], actto[t['act_to']][0])
+ graph.add_edge(pydot.Edge( activity_from ,activity_to, fontsize=8, **args))
+ nodes = cr.dictfetchall()
+ cr.execute('select id from wkf_activity where flow_start=True and wkf_id=%d limit 1', (wkf_id,))
+ start = cr.fetchone()[0]
+ cr.execute("select 'subflow.'||name,id from wkf_activity where flow_stop=True and wkf_id=%d", (wkf_id,))
+ stop = cr.fetchall()
+ stop = (stop[0][1], dict(stop))
+ return ((start,{}),stop)
+
+
+def graph_instance_get(cr, graph, inst_id, nested=False):
+ workitems = {}
+ cr.execute('select * from wkf_instance where id=%d', (inst_id,))
+ inst = cr.dictfetchone()
+
+ def workitem_get(instance):
+ cr.execute('select act_id,count(*) from wkf_workitem where inst_id=%d group by act_id', (instance,))
+ workitems = dict(cr.fetchall())
+
+ cr.execute('select subflow_id from wkf_workitem where inst_id=%d', (instance,))
+ for (subflow_id,) in cr.fetchall():
+ workitems.update(workitem_get(subflow_id))
+ return workitems
+ graph_get(cr, graph, inst['wkf_id'], nested, workitem_get(inst_id))
+
+#
+# TODO: pas clean: concurrent !!!
+#
+
+class report_graph_instance(object):
+ def __init__(self, cr, uid, ids, data):
+ try:
+ import pydot
+ except Exception,e:
+ print 'Import Error for pydot, you will not be able to render workflows'
+ print 'Consider Installing PyDot or dependencies: http://dkbza.org/pydot.html'
+ raise e
+ self.done = False
+
+ try:
+ cr.execute('select * from wkf where osv=%s limit 1', (data['model'],))
+ wkfinfo = cr.dictfetchone()
+ cr.execute('select id from wkf_instance where res_id=%d and wkf_id=%d order by state limit 1', (data['id'],wkfinfo['id']))
+ inst_id = cr.fetchone()[0]
+
+ graph = pydot.Dot(fontsize=16, label="\\n\\nWorkflow: %s\\n OSV: %s" % (wkfinfo['name'],wkfinfo['osv']))
+ graph_instance_get(cr, graph, inst_id, data.get('nested', False))
+ ps_string = graph.create_ps(prog='dot')
+ except:
+ # string is in PS, like the success message would have been
+ ps_string = '''%PS-Adobe-3.0
+/inch {72 mul} def
+/Times-Roman findfont 50 scalefont setfont
+1.5 inch 15 inch moveto
+(No workflow available) show
+showpage'''
+ input, output = os.popen2('ps2pdf -sPAPERSIZE=a3 - -')
+ input.write(ps_string)
+ input.close()
+ self.result = output.read()
+ output.close()
+ self.done = True
+
+ def is_done(self):
+ return self.done
+
+ def get(self):
+ if self.done:
+ return self.result
+ else:
+ return None
+
+class report_graph(report.interface.report_int):
+ def __init__(self, name, table):
+ report.interface.report_int.__init__(self, name)
+ self.table = table
+
+ def result(self):
+ if self.obj.is_done():
+ return (True, self.obj.get(), 'pdf')
+ else:
+ return (False, False, False)
+
+ def create(self, cr, uid, ids, data, context={}):
+ self.obj = report_graph_instance(cr, uid, ids, data)
+ return (self.obj.get(), 'pdf')
+
+report_graph('report.workflow.instance.graph', 'ir.workflow')
diff --git a/bin/addons/base/ir/workflow/pydot/LICENSE b/bin/addons/base/ir/workflow/pydot/LICENSE
new file mode 100755
index 00000000000..3291fa10051
--- /dev/null
+++ b/bin/addons/base/ir/workflow/pydot/LICENSE
@@ -0,0 +1,16 @@
+Copyright (c) 2004 Ero Carrera
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/bin/addons/base/ir/workflow/pydot/PKG-INFO b/bin/addons/base/ir/workflow/pydot/PKG-INFO
new file mode 100755
index 00000000000..098235a0e70
--- /dev/null
+++ b/bin/addons/base/ir/workflow/pydot/PKG-INFO
@@ -0,0 +1,33 @@
+Metadata-Version: 1.0
+Name: pydot
+Version: 0.9.9
+Summary: Python interface to Graphiz's Dot
+Home-page: http://dkbza.org/pydot.html
+Author: Ero Carrera
+Author-email: ero@dkbza.org
+License: MIT
+Description: Graphviz's dot language Python interface.
+
+ This module provides with a full interface to create handle modify
+ and process graphs in Graphviz's dot language.
+
+ References:
+
+ pydot Homepage: http://www.dkbza.org/pydot.html
+ Graphviz: http://www.research.att.com/sw/tools/graphviz/
+ DOT Language: http://www.research.att.com/~erg/graphviz/info/lang.html
+
+ Programmed and tested with Graphviz 1.12 and Python 2.3.3 on GNU/Linux
+ by Ero Carrera (c) 2004 [ero@dkbza.org]
+
+ Distributed under MIT license [http://opensource.org/licenses/mit-license.html].
+
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Science/Research
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Natural Language :: English
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Scientific/Engineering :: Visualization
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/bin/addons/base/ir/workflow/pydot/README b/bin/addons/base/ir/workflow/pydot/README
new file mode 100755
index 00000000000..d909fb9a225
--- /dev/null
+++ b/bin/addons/base/ir/workflow/pydot/README
@@ -0,0 +1,29 @@
+pydot - Python interface to Graphviz's Dot language
+Ero Carrera (c) 2004
+ero@dkbza.org
+
+This code is distributed under the MIT license.
+
+Requirements:
+-------------
+
+pyparsing: pydot requires the pyparsing module in order to be
+ able to load DOT files.
+
+GraphViz: is needed in order to render the graphs into any of
+ the plethora of output formats supported.
+
+Installation:
+-------------
+
+Should suffice with doing:
+
+ python setup.py install
+
+Needless to say, no installation is needed just to use the module. A mere:
+
+ import pydot
+
+should do it, provided that the directory containing the modules is on Python
+module search path.
+
diff --git a/bin/addons/base/ir/workflow/pydot/__init__.py b/bin/addons/base/ir/workflow/pydot/__init__.py
new file mode 100755
index 00000000000..6232f794cdd
--- /dev/null
+++ b/bin/addons/base/ir/workflow/pydot/__init__.py
@@ -0,0 +1 @@
+from pydot import *
diff --git a/bin/addons/base/ir/workflow/pydot/dot_parser.py b/bin/addons/base/ir/workflow/pydot/dot_parser.py
new file mode 100755
index 00000000000..30007ed47de
--- /dev/null
+++ b/bin/addons/base/ir/workflow/pydot/dot_parser.py
@@ -0,0 +1,352 @@
+#!/bin/env python
+
+"""
+The dotparser parses graphviz files in dot and dot files and transforms them
+into a class representation defined by pydot.
+
+The module needs pyparsing (tested with version 1.2) and pydot (tested with 0.9.9)
+
+Author: Michael Krause <michael@krause-software.de>
+"""
+
+import sys
+import glob
+import pydot
+
+from pyparsing import __version__ as pyparsing_version
+from pyparsing import Literal, CaselessLiteral, Word, \
+ Upcase, OneOrMore, ZeroOrMore, Forward, NotAny, \
+ delimitedList, oneOf, Group, Optional, Combine, \
+ alphas, nums, restOfLine, cStyleComment, nums, \
+ alphanums, printables, empty, quotedString, \
+ ParseException, ParseResults, CharsNotIn, _noncomma
+
+
+class P_AttrList:
+ def __init__(self, toks):
+ self.attrs = {}
+ i = 0
+ while i < len(toks):
+ attrname = toks[i]
+ attrvalue = toks[i+1]
+ self.attrs[attrname] = attrvalue
+ i += 2
+
+ def __repr__(self):
+ return "%s(%r)" % (self.__class__.__name__, self.attrs)
+
+
+class DefaultStatement(P_AttrList):
+ def __init__(self, default_type, attrs):
+ self.default_type = default_type
+ self.attrs = attrs
+
+ def __repr__(self):
+ return "%s(%s, %r)" % \
+ (self.__class__.__name__, self.default_type, self.attrs)
+
+
+def push_top_graph_stmt(str, loc, toks):
+ attrs = {}
+ g = None
+
+ for element in toks:
+ if isinstance(element, ParseResults) or \
+ isinstance(element, tuple) or \
+ isinstance(element, list):
+
+ element = element[0]
+
+ if element == 'strict':
+ attrs['strict'] = True
+ elif element in ['graph', 'digraph']:
+ attrs['graph_type'] = element
+ elif type(element) == type(''):
+ attrs['graph_name'] = element
+ elif isinstance(element, pydot.Graph):
+ g = pydot.Graph(**attrs)
+ g.__dict__.update(element.__dict__)
+ for e in g.get_edge_list():
+ e.parent_graph = g
+ for e in g.get_node_list():
+ e.parent_graph = g
+ for e in g.get_subgraph_list():
+ e.set_graph_parent(g)
+
+ elif isinstance(element, P_AttrList):
+ attrs.update(element.attrs)
+ else:
+ raise ValueError, "Unknown element statement: %r " % element
+
+ if g is not None:
+ g.__dict__.update(attrs)
+ return g
+
+
+def add_defaults(element, defaults):
+ d = element.__dict__
+ for key, value in defaults.items():
+ if not d.get(key):
+ d[key] = value
+
+
+def add_elements(g, toks, defaults_graph=None, defaults_node=None, defaults_edge=None):
+
+ if defaults_graph is None:
+ defaults_graph = {}
+ if defaults_node is None:
+ defaults_node = {}
+ if defaults_edge is None:
+ defaults_edge = {}
+
+ for element in toks:
+ if isinstance(element, pydot.Graph):
+ add_defaults(element, defaults_graph)
+ g.add_subgraph(element)
+ elif isinstance(element, pydot.Node):
+ add_defaults(element, defaults_node)
+ g.add_node(element)
+ elif isinstance(element, pydot.Edge):
+ add_defaults(element, defaults_edge)
+ g.add_edge(element)
+ elif isinstance(element, ParseResults):
+ for e in element:
+ add_elements(g, [e], defaults_graph, defaults_node, defaults_edge)
+ elif isinstance(element, DefaultStatement):
+ if element.default_type == 'graph':
+ default_graph_attrs = pydot.Node('graph')
+ default_graph_attrs.__dict__.update(element.attrs)
+ g.add_node(default_graph_attrs)
+# defaults_graph.update(element.attrs)
+# g.__dict__.update(element.attrs)
+ elif element.default_type == 'node':
+ default_node_attrs = pydot.Node('node')
+ default_node_attrs.__dict__.update(element.attrs)
+ g.add_node(default_node_attrs)
+ #defaults_node.update(element.attrs)
+ elif element.default_type == 'edge':
+ default_edge_attrs = pydot.Node('edge')
+ default_edge_attrs.__dict__.update(element.attrs)
+ g.add_node(default_edge_attrs)
+ #defaults_edge.update(element.attrs)
+ else:
+ raise ValueError, "Unknown DefaultStatement: %s " % element.default_type
+ elif isinstance(element, P_AttrList):
+ g.__dict__.update(element.attrs)
+ else:
+ raise ValueError, "Unknown element statement: %r " % element
+
+
+def push_graph_stmt(str, loc, toks):
+ g = pydot.Subgraph()
+ add_elements(g, toks)
+ return g
+
+
+def push_subgraph_stmt(str, loc, toks):
+ for e in toks:
+ if len(e)==3:
+ g = e[2]
+ g.set_name(e[1])
+
+ return g
+
+
+def push_default_stmt(str, loc, toks):
+ # The pydot class instances should be marked as
+ # default statements to be inherited by actual
+ # graphs, nodes and edges.
+ # print "push_default_stmt", toks
+ default_type = toks[0][0]
+ if len(toks) > 1:
+ attrs = toks[1].attrs
+ else:
+ attrs = {}
+
+ if default_type in ['graph', 'node', 'edge']:
+ return DefaultStatement(default_type, attrs)
+ else:
+ raise ValueError, "Unknown default statement: %r " % toks
+
+
+def push_attr_list(str, loc, toks):
+ p = P_AttrList(toks)
+ return p
+
+
+def get_port(node):
+
+ if len(node)>1:
+ if isinstance(node[1], ParseResults):
+ if len(node[1][0])==2:
+ if node[1][0][0]==':':
+ return node[1][0][1]
+
+ return None
+
+
+def push_edge_stmt(str, loc, toks):
+
+ tok_attrs = [a for a in toks if isinstance(a, P_AttrList)]
+ attrs = {}
+ for a in tok_attrs:
+ attrs.update(a.attrs)
+
+ n_prev = toks[0]
+ e = []
+ for n_next in tuple(toks)[2::2]:
+ port = get_port(n_prev)
+ if port is not None:
+ n_prev_port = ':'+port
+ else:
+ n_prev_port = ''
+
+ port = get_port(n_next)
+ if port is not None:
+ n_next_port = ':'+port
+ else:
+ n_next_port = ''
+
+ e.append(pydot.Edge(n_prev[0]+n_prev_port, n_next[0]+n_next_port, **attrs))
+ n_prev = n_next
+ return e
+
+
+def push_node_stmt(str, loc, toks):
+
+ if len(toks) == 2:
+ attrs = toks[1].attrs
+ else:
+ attrs = {}
+
+ node_name = toks[0]
+ if isinstance(node_name, list) or isinstance(node_name, tuple):
+ if len(node_name)>0:
+ node_name = node_name[0]
+
+ n = pydot.Node(node_name, **attrs)
+ return n
+
+
+def strip_quotes( s, l, t ):
+ return [ t[0].strip('"') ]
+
+
+graphparser = None
+def GRAPH_DEF():
+ global graphparser
+
+ if not graphparser:
+ # punctuation
+ colon = Literal(":")
+ lbrace = Literal("{")
+ rbrace = Literal("}")
+ lbrack = Literal("[")
+ rbrack = Literal("]")
+ lparen = Literal("(")
+ rparen = Literal(")")
+ equals = Literal("=")
+ comma = Literal(",")
+ dot = Literal(".")
+ slash = Literal("/")
+ bslash = Literal("\\")
+ star = Literal("*")
+ semi = Literal(";")
+ at = Literal("@")
+ minus = Literal("-")
+
+ # keywords
+ strict_ = Literal("strict")
+ graph_ = Literal("graph")
+ digraph_ = Literal("digraph")
+ subgraph_ = Literal("subgraph")
+ node_ = Literal("node")
+ edge_ = Literal("edge")
+
+ identifier = Word(alphanums + "_" ).setName("identifier")
+
+ double_quote = Literal('"')
+ double_quoted_string = \
+ Combine( double_quote + ZeroOrMore(CharsNotIn('"')) + double_quote )
+
+ alphastring_ = OneOrMore(CharsNotIn(_noncomma))
+
+ ID = (identifier | double_quoted_string.setParseAction(strip_quotes) |\
+ alphastring_).setName("ID")
+
+ html_text = Combine(Literal("<<") + OneOrMore(CharsNotIn(",]")))
+
+ float_number = Combine(Optional(minus) + \
+ OneOrMore(Word(nums + "."))).setName("float_number")
+
+ righthand_id = (float_number | ID | html_text).setName("righthand_id")
+
+ port_angle = (at + ID).setName("port_angle")
+
+ port_location = (Group(colon + ID) | \
+ Group(colon + lparen + ID + comma + ID + rparen)).setName("port_location")
+
+ port = (Group(port_location + Optional(port_angle)) | \
+ Group(port_angle + Optional(port_location))).setName("port")
+
+ node_id = (ID + Optional(port))
+ a_list = OneOrMore(ID + Optional(equals.suppress() + righthand_id) + \
+ Optional(comma.suppress())).setName("a_list")
+
+ attr_list = OneOrMore(lbrack.suppress() + Optional(a_list) + \
+ rbrack.suppress()).setName("attr_list")
+
+ attr_stmt = (Group(graph_ | node_ | edge_) + attr_list).setName("attr_stmt")
+
+ edgeop = (Literal("--") | Literal("->")).setName("edgeop")
+
+ stmt_list = Forward()
+ graph_stmt = Group(lbrace.suppress() + Optional(stmt_list) + \
+ rbrace.suppress()).setName("graph_stmt")
+
+ subgraph = (Group(Optional(subgraph_ + Optional(ID)) + graph_stmt) | \
+ Group(subgraph_ + ID)).setName("subgraph")
+
+ edgeRHS = OneOrMore(edgeop + Group(node_id | subgraph))
+
+ edge_stmt = Group(node_id | subgraph) + edgeRHS + Optional(attr_list)
+
+ node_stmt = (node_id + Optional(attr_list) + semi.suppress()).setName("node_stmt")
+
+ assignment = (ID + equals.suppress() + righthand_id).setName("assignment")
+ stmt = (assignment | edge_stmt | attr_stmt | node_stmt | subgraph).setName("stmt")
+ stmt_list << OneOrMore(stmt + Optional(semi.suppress()))
+
+ graphparser = (Optional(strict_) + Group((graph_ | digraph_)) + \
+ Optional(ID) + graph_stmt).setResultsName("graph")
+
+ singleLineComment = "//" + restOfLine
+ graphparser.ignore(singleLineComment)
+ graphparser.ignore(cStyleComment)
+
+ assignment.setParseAction(push_attr_list)
+ a_list.setParseAction(push_attr_list)
+ edge_stmt.setParseAction(push_edge_stmt)
+ node_stmt.setParseAction(push_node_stmt)
+ attr_stmt.setParseAction(push_default_stmt)
+
+ subgraph.setParseAction(push_subgraph_stmt)
+ graph_stmt.setParseAction(push_graph_stmt)
+ graphparser.setParseAction(push_top_graph_stmt)
+
+ return graphparser
+
+
+def parse_dot_data(data):
+ try:
+ graphparser = GRAPH_DEF()
+ if pyparsing_version >= '1.2':
+ graphparser.parseWithTabs()
+ tokens = graphparser.parseString(data)
+ graph = tokens.graph
+ return graph
+ except ParseException, err:
+ print err.line
+ print " "*(err.column-1) + "^"
+ print err
+ return None
diff --git a/bin/addons/base/ir/workflow/pydot/pydot.py b/bin/addons/base/ir/workflow/pydot/pydot.py
new file mode 100755
index 00000000000..947099f3ca0
--- /dev/null
+++ b/bin/addons/base/ir/workflow/pydot/pydot.py
@@ -0,0 +1,1075 @@
+# -*- coding: Latin-1 -*-
+"""Graphviz's dot language Python interface.
+
+This module provides with a full interface to create handle modify
+and process graphs in Graphviz's dot language.
+
+References:
+
+pydot Homepage: http://www.dkbza.org/pydot.html
+Graphviz: http://www.research.att.com/sw/tools/graphviz/
+DOT Language: http://www.research.att.com/~erg/graphviz/info/lang.html
+
+Programmed and tested with Graphviz 1.12 and Python 2.3.3 on GNU/Linux
+by Ero Carrera (c) 2004 [ero@dkbza.org]
+
+Distributed under MIT license [http://opensource.org/licenses/mit-license.html].
+"""
+
+__author__ = 'Ero Carrera'
+__version__ = '0.9.9'
+__license__ = 'MIT'
+
+import os
+import tempfile
+import copy
+import dot_parser
+
+
+def graph_from_dot_data(data):
+ """Load graph as defined by data in DOT format.
+
+ The data is assumed to be in DOT format. It will
+ be parsed and a Dot class will be returned,
+ representing the graph.
+ """
+
+ graph = dot_parser.parse_dot_data(data)
+ if graph is not None:
+ dot = Dot()
+ dot.__dict__.update(graph.__dict__)
+ return dot
+
+ return None
+
+def graph_from_dot_file(path):
+ """Load graph as defined by a DOT file.
+
+ The file is assumed to be in DOT format. It will
+ be loaded, parsed and a Dot class will be returned,
+ representing the graph.
+ """
+
+ fd = file(path, 'rb')
+ data = fd.read()
+ fd.close()
+
+ return graph_from_dot_data(data)
+
+
+def graph_from_edges(edge_list, node_prefix='', directed=False):
+ """Creates a basic graph out of an edge list.
+
+ The edge list has to be a list of tuples representing
+ the nodes connected by the edge.
+ The values can be anything: bool, int, float, str.
+
+ If the graph is undirected by default, it is only
+ calculated from one of the symmetric halves of the matrix.
+ """
+ if directed:
+ graph = Dot(graph_type='digraph')
+ else:
+ graph = Dot(graph_type='graph')
+ for edge in edge_list:
+ e = Edge(node_prefix+str(edge[0]), node_prefix+str(edge[1]))
+ graph.add_edge(e)
+ return graph
+
+
+def graph_from_adjacency_matrix(matrix, node_prefix='', directed=False):
+ """Creates a basic graph out of an adjacency matrix.
+
+ The matrix has to be a list of rows of values
+ representing an adjacency matrix.
+ The values can be anything: bool, int, float, as long
+ as they can evaluate to True or False.
+ """
+ node_orig = 1
+ if directed:
+ graph = Dot(graph_type='digraph')
+ else:
+ graph = Dot(graph_type='graph')
+ for row in matrix:
+ if not directed:
+ skip = matrix.index(row)
+ r = row[skip:]
+ else:
+ skip = 0
+ r = row
+ node_dest = skip+1
+ for e in r:
+ if e:
+ graph.add_edge( \
+ Edge( node_prefix+str(node_orig), \
+ node_prefix+str(node_dest)))
+ node_dest += 1
+ node_orig += 1
+ return graph
+
+def graph_from_incidence_matrix(matrix, node_prefix='', directed=False):
+ """Creates a basic graph out of an incidence matrix.
+
+ The matrix has to be a list of rows of values
+ representing an incidence matrix.
+ The values can be anything: bool, int, float, as long
+ as they can evaluate to True or False.
+ """
+ node_orig = 1
+ if directed:
+ graph = Dot(graph_type='digraph')
+ else:
+ graph = Dot(graph_type='graph')
+ for row in matrix:
+ nodes = []
+ c = 1
+ for node in row:
+ if node:
+ nodes.append(c*node)
+ c += 1
+ nodes.sort()
+ if len(nodes) == 2:
+ graph.add_edge( \
+ Edge(node_prefix+str(abs(nodes[0])), \
+ node_prefix+str(nodes[1]) ))
+ if not directed:
+ graph.set_simplify(True)
+ return graph
+
+
+def find_graphviz():
+ """Locate Graphviz's executables in the system.
+
+ Attempts to locate graphviz's executables in a Unix system.
+ It will look for 'dot', 'twopi' and 'neato' in all the directories
+ specified in the PATH environment variable.
+ It will return a dictionary containing the program names as keys
+ and their paths as values.
+ """
+ progs = {'dot': '', 'twopi': '', 'neato': '', 'circo': '', 'fdp': ''}
+ if not os.environ.has_key('PATH'):
+ return None
+ for path in os.environ['PATH'].split(os.pathsep):
+ for prg in progs.keys():
+ if os.path.exists(path+os.path.sep+prg):
+ progs[prg] = path+os.path.sep+prg
+ elif os.path.exists(path+os.path.sep+prg + '.exe'):
+ progs[prg] = path+os.path.sep+prg + '.exe'
+ return progs
+
+class Common:
+ """Common information to several classes.
+
+ Should not be directly used, several classes are derived from
+ this one.
+ """
+ chars_ID = None
+ parent_graph = None
+
+ def char_range(self, a,b):
+ """Generate a list containing a range of characters.
+
+ Returns a list of characters starting from 'a' up to 'b'
+ both inclusive.
+ """
+ return map(chr, range(ord(a), ord(b)+1))
+
+ def is_ID(self, s):
+ """Checks whether a string is an dot language ID.
+
+ It will check whether the string is solely composed
+ by the characters allowed in an ID or not.
+ """
+ if not self.chars_ID:
+ self.chars_ID = self.char_range('a','z')+self.char_range('A','Z')+ \
+ self.char_range('0','9')+['_']
+ for c in s:
+ if c not in self.chars_ID:
+ return False
+ return True
+
+class Error(Exception):
+ """General error handling class.
+ """
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return self.value
+
+
+class Node(object, Common):
+ """A graph node.
+
+ This class represents a graph's node with all its attributes.
+
+ node(name, attribute=value, ...)
+
+ name: node's name
+
+ All the attributes defined in the Graphviz dot language should
+ be supported.
+ """
+ attributes = ['showboxes', 'URL', 'fontcolor', 'fontsize', 'label', 'fontname', \
+ 'comment', 'root', 'toplabel', 'vertices', 'width', 'z', 'bottomlabel', \
+ 'distortion', 'fixedsize', 'group', 'height', 'orientation', 'pin', \
+ 'rects', 'regular', 'shape', 'shapefile', 'sides', 'skew', 'pos', \
+ 'layer', 'tooltip', 'style', 'target', 'color', 'peripheries',
+ 'fillcolor']
+
+ def __init__(self, name, **attrs):
+
+ if isinstance(name, str) and not name.startswith('"'):
+ idx = name.find(':')
+ if idx>0:
+ name = name[:idx]
+
+ self.name = str(name)
+ for attr in self.attributes:
+ # Set all the attributes to None.
+ self.__setattr__(attr, None)
+ # Generate all the Setter methods.
+ self.__setattr__('set_'+attr, lambda x, a=attr : self.__setattr__(a, x))
+ # Generate all the Getter methods.
+ self.__setattr__('get_'+attr, lambda a=attr : self.__getattribute__(a))
+ for attr, val in attrs.items():
+ self.__setattr__(attr, val)
+
+ def set_name(self, node_name):
+ """Set the node's name."""
+
+ self.name = str(node_name)
+
+ def get_name(self):
+ """Get the node's name."""
+
+ return self.name
+
+ def set(self, name, value):
+ """Set an attribute value by name.
+
+ Given an attribute 'name' it will set its value to 'value'.
+ There's always the possibility of using the methods:
+ set_'name'(value)
+ which are defined for all the existing attributes.
+ """
+ if name in self.attributes:
+ self.__dict__[name] = value
+ return True
+ # Attribute is not known
+ return False
+
+ def to_string(self):
+ """Returns a string representation of the node in dot language.
+ """
+
+ if not isinstance(self.name, str):
+ self.name = str(self.name)
+
+ # RMF: special case defaults for node, edge and graph properties.
+ if self.name in ['node', 'edge', 'graph']:
+ node = self.name
+ else:
+ node = '"'+self.name+'"'
+
+ node_attr = None
+ all_attrs = [a for a in self.attributes]
+ all_attrs += [a for a in Graph.attributes if a not in all_attrs]
+ all_attrs += [a for a in Edge.attributes if a not in all_attrs]
+ for attr in all_attrs:
+ if self.__dict__.has_key(attr) \
+ and self.__getattribute__(attr) is not None:
+ if not node_attr:
+ node_attr = ''
+ else:
+ node_attr += ', '
+ node_attr += attr+'='
+ val = self.__dict__[attr]
+
+ if val.startswith('<') and val.endswith('>'):
+ node_attr += val
+ elif (isinstance(val, str) or isinstance(val, unicode)) and not self.is_ID(val):
+ node_attr += '"'+val+'"'
+ else:
+ node_attr += str(val)
+
+ if node_attr:
+ node += ' ['+node_attr+']'
+ node += ';'
+
+ return node
+
+
+class Edge(object, Common):
+ """A graph edge.
+
+ This class represents a graph's edge with all its attributes.
+
+ edge(src, dst, attribute=value, ...)
+
+ src: source node's name
+ dst: destination node's name
+
+ All the attributes defined in the Graphviz dot language should
+ be supported.
+
+ Attributes can be set through the dynamically generated methods:
+
+ set_[attribute name], i.e. set_label, set_fontname
+
+ or using the instance's attributes:
+
+ Edge.[attribute name], i.e. edge_instance.label, edge_instance.fontname
+ """
+ attributes = ['style', 'target', 'pos', 'layer', 'tooltip', 'color', 'showboxes',\
+ 'URL', 'fontcolor', 'fontsize', 'label', 'fontname', 'comment', 'lp', \
+ 'arrowhead', 'arrowsize', 'arrowtail', 'constraint', 'decorate', 'dir', \
+ 'headURL', 'headclip', 'headhref', 'headlabel', 'headport', \
+ 'headtarget', 'headtooltip', 'href', 'labelangle', 'labeldistance', \
+ 'labelfloat', 'labelfontcolor', 'labelfontname', 'labelfontsize', 'len',\
+ 'lhead', 'ltail', 'minlen', 'samehead', 'sametail', 'weight', 'tailURL',\
+ 'tailclip', 'tailhref', 'taillabel', 'tailport', 'tailtarget', \
+ 'tailtooltip']
+
+ def __init__(self, src, dst, **attrs):
+ self.src = src
+ self.dst = dst
+ for attr in self.attributes:
+ # Set all the attributes to None.
+ self.__setattr__(attr, None)
+ # Generate all the Setter methods.
+ self.__setattr__('set_'+attr, lambda x, a=attr : self.__setattr__(a, x))
+ # Generate all the Getter methods.
+ self.__setattr__('get_'+attr, lambda a=attr : self.__getattribute__(a))
+ for attr, val in attrs.items():
+ self.__setattr__(attr, val)
+
+ def get_source(self):
+ """Get the edges source node name."""
+
+ return self.src
+
+ def get_destination(self):
+ """Get the edge's destination node name."""
+
+ return self.dst
+
+ def __eq__(self, edge):
+ """Compare two edges.
+
+ If the parent graph is directed, arcs linking
+ node A to B are considered equal and A->B != B->A
+
+ If the parent graph is undirected, any edge
+ connecting two nodes is equal to any other
+ edge connecting the same nodes, A->B == B->A
+ """
+
+ if not isinstance(edge, Edge):
+ raise Error, 'Can\'t compare and edge to a non-edge object.'
+ if self.parent_graph.graph_type=='graph':
+ # If the graph is undirected, the edge has neither
+ # source nor destination.
+ if (self.src==edge.src and self.dst==edge.dst) or \
+ (self.src==edge.dst and self.dst==edge.src):
+ return True
+ else:
+ if self.src==edge.src and self.dst==edge.dst:
+ return True
+ return False
+
+
+ def set(self, name, value):
+ """Set an attribute value by name.
+
+ Given an attribute 'name' it will set its value to 'value'.
+ There's always the possibility of using the methods:
+ set_'name'(value)
+ which are defined for all the existing attributes.
+ """
+ if name in self.attributes:
+ self.__dict__[name] = value
+ return True
+ # Attribute is not known
+ return False
+
+
+ def parse_node_ref(self, node_str):
+
+ if not isinstance(node_str, str):
+ node_str = str(node_str)
+
+ if node_str[0]=='"' and node_str[-1]=='"' and node_str[0].count('"')%2!=0:
+ return node_str
+
+ node_port_idx = node_str.rfind(':')
+
+ if node_port_idx>0 and node_str[0]=='"' and node_str[node_port_idx-1]=='"':
+ return node_str
+
+ node_str = node_str.replace('"', '')
+
+ if node_port_idx>0:
+ a = node_str[:node_port_idx]
+ b = node_str[node_port_idx+1:]
+ if self.is_ID(a):
+ node = a
+ else:
+ node = '"'+a+'"'
+ if self.is_ID(b):
+ node += ':'+b
+ else:
+ node+=':"'+b+'"'
+ return node
+
+ return '"'+node_str+'"'
+
+
+ def to_string(self):
+ """Returns a string representation of the edge in dot language.
+ """
+
+ src = self.parse_node_ref(self.src)
+ dst = self.parse_node_ref(self.dst)
+
+ edge = src
+ if self.parent_graph and \
+ self.parent_graph.graph_type and \
+ self.parent_graph.graph_type=='digraph':
+ edge+=' -> '
+ else:
+ edge+=' -- '
+ edge+=dst
+
+ edge_attr = None
+ for attr in self.attributes:
+ if self.__dict__.has_key(attr) \
+ and self.__getattribute__(attr) is not None:
+ if not edge_attr:
+ edge_attr = ''
+ else:
+ edge_attr+=', '
+ edge_attr+=attr+'='
+ val = self.__dict__[attr]
+ if (isinstance(val, str) or isinstance(val, unicode)) and not self.is_ID(val):
+ edge_attr+='"'+val+'"'
+ else:
+ edge_attr+=str(val)
+
+ if edge_attr:
+ edge+=' ['+edge_attr+']'
+ edge+=';'
+
+ return edge
+
+class Graph(object, Common):
+
+ """Class representing a graph in Graphviz's dot language.
+
+ This class implements the methods to work on a representation
+ of a graph in Graphviz's dot language.
+
+ graph(graph_name='G', type='digraph', strict=False, suppress_disconnected=False, attribute=value, ...)
+
+ graph_name:
+ the graph's name
+ type:
+ can be 'graph' or 'digraph'
+ suppress_disconnected:
+ defaults to False, which will remove from the
+ graph any disconnected nodes.
+ simplify:
+ if True it will avoid displaying equal edges, i.e.
+ only one edge between two nodes. removing the
+ duplicated ones.
+
+ All the attributes defined in the Graphviz dot language should
+ be supported.
+
+ Attributes can be set through the dynamically generated methods:
+
+ set_[attribute name], i.e. set_size, set_fontname
+
+ or using the instance's attributes:
+
+ Graph.[attribute name], i.e. graph_instance.label, graph_instance.fontname
+ """
+
+ attributes = ['Damping', 'bb', 'center', 'clusterrank', 'compound', 'concentrate',\
+ 'defaultdist', 'dim', 'fontpath', 'epsilon', 'layers', 'layersep', \
+ 'margin', 'maxiter', 'mclimit', 'mindist', 'pack', 'packmode', 'model', \
+ 'page', 'pagedir', 'nodesep', 'normalize', 'nslimit1', 'ordering', \
+ 'orientation', 'outputorder', 'overlap', 'remincross', 'resolution', \
+ 'rankdir', 'ranksep', 'ratio', 'rotate', 'samplepoints', 'searchsize', \
+ 'sep', 'size', 'splines', 'start', 'stylesheet', 'truecolor', \
+ 'viewport', 'voro_margin', 'quantum', 'bgcolor', 'labeljust', \
+ 'labelloc', 'root', 'showboxes', 'URL', 'fontcolor', 'fontsize', \
+ 'label' ,'fontname', 'comment', 'lp', 'target', 'color', 'style', \
+ 'concentrators']
+
+ def __init__(self, graph_name='G', graph_type='digraph', strict=False, \
+ suppress_disconnected=False, simplify=False, **attrs):
+
+ if graph_type not in ['graph', 'digraph']:
+ raise Error, 'Invalid type. Accepted graph types are: graph, digraph, subgraph'
+ self.graph_type = graph_type
+ self.graph_name = graph_name
+ self.strict = strict
+ self.suppress_disconnected = suppress_disconnected
+ self.simplify = simplify
+ self.node_list = []
+ self.edge_list = []
+ self.edge_src_list = []
+ self.edge_dst_list = []
+ self.subgraph_list = []
+ self.parent_graph = self
+ for attr in self.attributes:
+ # Set all the attributes to None.
+ self.__setattr__(attr, None)
+ # Generate all the Setter methods.
+ self.__setattr__('set_'+attr, lambda x, a=attr : self.__setattr__(a, x))
+ # Generate all the Getter methods.
+ self.__setattr__('get_'+attr, lambda a=attr : self.__getattribute__(a))
+ for attr, val in attrs.items():
+ self.__setattr__(attr, val)
+
+ def set_simplify(self, simplify):
+ """Set whether to simplify or not.
+
+ If True it will avoid displaying equal edges, i.e.
+ only one edge between two nodes. removing the
+ duplicated ones.
+ """
+
+ self.simplify = simplify
+
+ def get_simplify(self):
+ """Get whether to simplify or not.
+
+ Refer to set_simplify for more information.
+ """
+
+ return self.simplify
+
+
+ def set_type(self, graph_type):
+ """Set the graph's type, 'graph' or 'digraph'."""
+ self.graph_type = graph_type
+
+ def get_type(self):
+ """Get the graph's type, 'graph' or 'digraph'."""
+ return self.graph_type
+
+ def set_name(self, graph_name):
+ """Set the graph's name."""
+
+ self.graph_name = graph_name
+
+ def get_name(self):
+ """Get the graph's name."""
+
+ return self.graph_name
+
+ def set_strict(self, val):
+ """Set graph to 'strict' mode.
+
+ This option is only valid for top level graphs.
+ """
+
+ self.strict = val
+
+ def get_strict(self, val):
+ """Get graph's 'strict' mode (True, False).
+
+ This option is only valid for top level graphs.
+ """
+
+ return self.strict
+
+ def set_suppress_disconnected(self, val):
+ """Suppress disconnected nodes in the output graph.
+
+ This option will skip nodes in the graph with no incoming or outgoing
+ edges. This option works also for subgraphs and has effect only in the
+ current graph/subgraph.
+ """
+
+ self.suppress_disconnected = val
+
+ def get_suppress_disconnected(self, val):
+ """Get if suppress disconnected is set.
+
+ Refer to set_suppress_disconnected for more information.
+ """
+
+ self.suppress_disconnected = val
+
+ def set(self, name, value):
+ """Set an attribute value by name.
+
+ Given an attribute 'name' it will set its value to 'value'.
+ There's always the possibility of using the methods:
+
+ set_'name'(value)
+
+ which are defined for all the existing attributes.
+ """
+ if name in self.attributes:
+ self.__dict__[name] = value
+ return True
+ # Attribute is not known
+ return False
+
+ def get(self, name):
+ """Get an attribute value by name.
+
+ Given an attribute 'name' it will get its value.
+ There's always the possibility of using the methods:
+
+ get_'name'()
+
+ which are defined for all the existing attributes.
+ """
+ return self.__dict__[name]
+
+ def add_node(self, graph_node):
+ """Adds a node object to the graph.
+
+ It takes a node object as its only argument and returns
+ None.
+ """
+
+ if not isinstance(graph_node, Node):
+ raise Error, 'add_node received a non node class object'
+
+ if self.get_node(graph_node.get_name()) is None:
+ self.node_list.append(graph_node)
+ graph_node.parent_graph = self.parent_graph
+
+ def get_node(self, name):
+ """Retrieved a node from the graph.
+
+ Given a node's name the corresponding Node
+ instance will be returned.
+
+ If multiple nodes exist with that name, a list of
+ Node instances is returned.
+ If only one node exists, the instance is returned.
+ None is returned otherwise.
+ """
+
+ match = [node for node in self.node_list if node.get_name() == str(name)]
+
+ l = len(match)
+ if l==1:
+ return match[0]
+ elif l>1:
+ return match
+ else:
+ return None
+
+ def get_node_list(self):
+ """Get the list of Node instances.
+
+ This method returns the list of Node instances
+ composing the graph.
+ """
+
+ return self.node_list
+
+ def add_edge(self, graph_edge):
+ """Adds an edge object to the graph.
+
+ It takes a edge object as its only argument and returns
+ None.
+ """
+
+ if not isinstance(graph_edge, Edge):
+ raise Error, 'add_edge received a non edge class object'
+
+ self.edge_list.append(graph_edge)
+
+ src = self.get_node(graph_edge.get_source())
+ if src is None:
+ self.add_node(Node(graph_edge.get_source()))
+
+ dst = self.get_node(graph_edge.get_destination())
+ if dst is None:
+ self.add_node(Node(graph_edge.get_destination()))
+
+ graph_edge.parent_graph = self.parent_graph
+
+ if graph_edge.src not in self.edge_src_list:
+ self.edge_src_list.append(graph_edge.src)
+
+ if graph_edge.dst not in self.edge_dst_list:
+ self.edge_dst_list.append(graph_edge.dst)
+
+ def get_edge(self, src, dst):
+ """Retrieved an edge from the graph.
+
+ Given an edge's source and destination the corresponding
+ Edge instance will be returned.
+
+ If multiple edges exist with that source and destination,
+ a list of Edge instances is returned.
+ If only one edge exists, the instance is returned.
+ None is returned otherwise.
+ """
+
+ match = [edge for edge in self.edge_list if edge.src == src and edge.dst == dst]
+
+ l = len(match)
+ if l==1:
+ return match[0]
+ elif l>1:
+ return match
+ else:
+ return None
+
+ def get_edge_list(self):
+ """Get the list of Edge instances.
+
+ This method returns the list of Edge instances
+ composing the graph.
+ """
+
+ return self.edge_list
+
+ def add_subgraph(self, sgraph):
+ """Adds an edge object to the graph.
+
+ It takes a subgraph object as its only argument and returns
+ None.
+ """
+ if not isinstance(sgraph, Subgraph) and not isinstance(sgraph, Cluster):
+ raise Error, 'add_subgraph received a non subgraph class object'
+
+ self.subgraph_list.append(sgraph)
+
+ sgraph.set_graph_parent(self.parent_graph)
+ return None
+
+ def get_subgraph(self, name):
+ """Retrieved a subgraph from the graph.
+
+ Given a subgraph's name the corresponding
+ Subgraph instance will be returned.
+
+ If multiple subgraphs exist with the same name, a list of
+ Subgraph instances is returned.
+ If only one Subgraph exists, the instance is returned.
+ None is returned otherwise.
+ """
+
+ match = [sgraph for sgraph in self.subgraph_list if sgraph.graph_name == name]
+
+ l = len(match)
+ if l==1:
+ return match[0]
+ elif l>1:
+ return match
+ else:
+ return None
+
+ def get_subgraph_list(self):
+ """Get the list of Subgraph instances.
+
+ This method returns the list of Subgraph instances
+ in the graph.
+ """
+
+ return self.subgraph_list
+
+ def set_graph_parent(self, parent):
+ """Sets a graph and its elements to point the the parent.
+
+ Any subgraph added to a parent graph receives a reference
+ to the parent to access some common data.
+ """
+ self.parent_graph = parent
+
+ for elm in self.edge_list:
+ elm.parent_graph = parent
+
+ for elm in self.node_list:
+ elm.parent_graph = parent
+
+ for elm in self.subgraph_list:
+ elm.parent_graph = parent
+ elm.set_graph_parent(parent)
+
+ def to_string(self, indent=''):
+ """Returns a string representation of the graph in dot language.
+
+ It will return the graph and all its subelements in string from.
+ """
+ graph = indent+''
+ if self.__dict__.has_key('strict'):
+ if self==self.parent_graph and self.strict:
+ graph+='strict '
+
+ graph+=self.graph_type+' '+self.graph_name+' {\n'
+
+ for attr in self.attributes:
+ if self.__dict__.has_key(attr) \
+ and self.__getattribute__(attr) is not None:
+ graph += indent+'\t'+attr+'='
+ val = self.__dict__[attr]
+ if isinstance(val, str) and not self.is_ID(val):
+ graph += '"'+val+'"'
+ else:
+ graph += str(val)
+ graph+=';\n'
+
+ # RMF: for graphviz to handle edge/node defaults correctly,
+ # it seems they must appear before the rest of the nodes/edges
+ # so if they exist, dump them out first.
+ for elm in self.node_list:
+ if elm.name in ['node', 'edge', 'graph']:
+ graph += indent+'\t'+elm.to_string()+'\n'
+
+ for elm in self.subgraph_list:
+ graph += elm.to_string(indent+'\t')+'\n'
+
+ for elm in self.node_list:
+ # RMF: don't repeat the node/edge defaults
+ if elm.name in ['node', 'edge', 'graph']:
+ continue
+ if self.suppress_disconnected:
+ if elm.name not in self.edge_src_list+self.edge_dst_list:
+ continue
+ graph+=indent+'\t'+elm.to_string()+'\n'
+
+ edges_done = []
+ for elm in self.edge_list:
+ if self.simplify and elm in edges_done:
+ continue
+ graph+=indent+'\t'+elm.to_string()+'\n'
+ edges_done.append(elm)
+
+ graph+=indent+'}\n'
+
+ return graph
+
+
+class Subgraph(Graph):
+
+ """Class representing a subgraph in Graphviz's dot language.
+
+ This class implements the methods to work on a representation
+ of a subgraph in Graphviz's dot language.
+
+ subgraph(graph_name='subG', suppress_disconnected=False, attribute=value, ...)
+
+ graph_name:
+ the subgraph's name
+ suppress_disconnected:
+ defaults to false, which will remove from the
+ subgraph any disconnected nodes.
+ All the attributes defined in the Graphviz dot language should
+ be supported.
+
+ Attributes can be set through the dynamically generated methods:
+
+ set_[attribute name], i.e. set_size, set_fontname
+
+ or using the instance's attributes:
+
+ Subgraph.[attribute name], i.e.
+ subgraph_instance.label, subgraph_instance.fontname
+ """
+
+ attributes = Graph.attributes+['rank']
+
+ # RMF: subgraph should have all the attributes of graph so it can be passed
+ # as a graph to all methods
+ def __init__(self, graph_name='subG', suppress_disconnected=False, \
+ simplify=False, **attrs):
+
+ self.graph_type = 'subgraph'
+ self.graph_name = graph_name
+ self.suppress_disconnected = suppress_disconnected
+ self.simplify = simplify
+ self.node_list = []
+ self.edge_list = []
+ self.edge_src_list = []
+ self.edge_dst_list = []
+ self.subgraph_list = []
+ for attr in self.attributes:
+ # Set all the attributes to None.
+ self.__setattr__(attr, None)
+ # Generate all the Setter methods.
+ self.__setattr__('set_'+attr, lambda x, a=attr : self.__setattr__(a, x))
+ # Generate all the Getter methods.
+ self.__setattr__('get_'+attr, lambda a=attr : self.__getattribute__(a))
+ for attr, val in attrs.items():
+ self.__setattr__(attr, val)
+
+
+class Cluster(Graph):
+
+ """Class representing a cluster in Graphviz's dot language.
+
+ This class implements the methods to work on a representation
+ of a cluster in Graphviz's dot language.
+
+ cluster(graph_name='subG', suppress_disconnected=False, attribute=value, ...)
+
+ graph_name:
+ the cluster's name (the string 'cluster' will be always prepended)
+ suppress_disconnected:
+ defaults to false, which will remove from the
+ cluster any disconnected nodes.
+ All the attributes defined in the Graphviz dot language should
+ be supported.
+
+ Attributes can be set through the dynamically generated methods:
+
+ set_[attribute name], i.e. set_color, set_fontname
+
+ or using the instance's attributes:
+
+ Cluster.[attribute name], i.e.
+ cluster_instance.color, cluster_instance.fontname
+ """
+
+ attributes = ['pencolor', 'bgcolor', 'labeljust', 'labelloc', 'URL', 'fontcolor', \
+ 'fontsize', 'label', 'fontname', 'lp', 'style', 'target', 'color', \
+ 'peripheries', 'fillcolor']
+
+ def __init__(self, graph_name='subG', suppress_disconnected=False, \
+ simplify=False, **attrs):
+
+ #if type not in ['subgraph']:
+ # raise Error, 'Invalid type. Accepted graph types are: subgraph'
+ self.graph_type = 'subgraph'
+ self.graph_name = 'cluster_'+graph_name
+ self.suppress_disconnected = suppress_disconnected
+ self.simplify = simplify
+ self.node_list = []
+ self.edge_list = []
+ self.edge_src_list = []
+ self.edge_dst_list = []
+ self.subgraph_list = []
+ for attr in self.attributes:
+ # Set all the attributes to None.
+ self.__setattr__(attr, None)
+ # Generate all the Setter methods.
+ self.__setattr__('set_'+attr, lambda x, a=attr : self.__setattr__(a, x))
+ # Generate all the Getter methods.
+ self.__setattr__('get_'+attr, lambda a=attr : self.__getattribute__(a))
+ for attr, val in attrs.items():
+ self.__setattr__(attr, val)
+
+
+class Dot(Graph):
+ """A container for handling a dot language file.
+
+ This class implements methods to write and process
+ a dot language file. It is a derived class of
+ the base class 'Graph'.
+ """
+
+ progs = None
+
+ formats = ['ps', 'ps2', 'hpgl', 'pcl', 'mif', 'pic', 'gd', 'gd2', 'gif', 'jpg', \
+ 'jpeg', 'png', 'wbmp', 'ismap', 'imap', 'cmap', 'vrml', 'vtx', 'mp', \
+ 'fig', 'svg', 'svgz', 'dia', 'dot', 'canon', 'plain', 'plain-ext', 'xdot']
+
+ def __init__(self, prog='dot', **args):
+ Graph.__init__(self, **args)
+
+ self.prog = prog
+
+ # Automatically creates all the methods enabling the creation
+ # of output in any of the supported formats.
+ for frmt in self.formats:
+ self.__setattr__('create_'+frmt, lambda f=frmt, prog=self.prog : self.create(format=f, prog=prog))
+ f = self.__dict__['create_'+frmt]
+ f.__doc__ = '''Refer to docstring from 'create' for more information.'''
+
+ for frmt in self.formats+['raw']:
+ self.__setattr__('write_'+frmt, lambda path, f=frmt, prog=self.prog : self.write(path, format=f, prog=prog))
+ f = self.__dict__['write_'+frmt]
+ f.__doc__ = '''Refer to docstring from 'write' for more information.'''
+
+ def set_prog(self, prog):
+ """Sets the default program.
+
+ Sets the default program in charge of processing
+ the dot file into a graph.
+ """
+ self.prog = prog
+
+ def write(self, path, prog=None, format='raw'):
+ """Writes a graph to a file.
+
+ Given a filename 'path' it will open/create and truncate
+ such file and write on it a representation of the graph
+ defined by the dot object and in the format specified by
+ 'format'.
+ The format 'raw' is used to dump the string representation
+ of the Dot object, without further processing.
+ The output can be processed by any of graphviz tools, defined
+ in 'prog', which defaults to 'dot'
+ Returns True or False according to the success of the write
+ operation.
+
+ There's also the preferred possibility of using:
+
+ write_'format'(path, prog='program')
+
+ which are automatically defined for all the supported formats.
+ [write_ps(), write_gif(), write_dia(), ...]
+ """
+
+ if prog is None:
+ prog = self.prog
+
+ dot_fd = file(path, "w+b")
+ if format == 'raw':
+ dot_fd.write(self.to_string())
+ else:
+ dot_fd.write(self.create(prog, format))
+ dot_fd.close()
+
+ return True
+
+ def create(self, prog=None, format='ps'):
+ """Creates and returns a Postscript representation of the graph.
+
+ create will write the graph to a temporary dot file and process
+ it with the program given by 'prog' (which defaults to 'twopi'),
+ reading the Postscript output and returning it as a string is the
+ operation is successful.
+ On failure None is returned.
+
+ There's also the preferred possibility of using:
+
+ create_'format'(prog='program')
+
+ which are automatically defined for all the supported formats.
+ [create_ps(), create_gif(), create_dia(), ...]
+ """
+ if prog is None:
+ prog = self.prog
+
+ if self.progs is None:
+ self.progs = find_graphviz()
+ if self.progs is None:
+ return None
+ if not self.progs.has_key(prog):
+ # Program not found ?!?!
+ return None
+
+ tmp_fd, tmp_name = tempfile.mkstemp()
+ os.close(tmp_fd)
+ self.write(tmp_name)
+ stdin, stdout, stderr = os.popen3(self.progs[prog]+' -T'+format+' '+tmp_name, 'b')
+ stdin.close()
+ stderr.close()
+ data = stdout.read()
+ stdout.close()
+ os.unlink(tmp_name)
+ return data
+
diff --git a/bin/addons/base/ir/workflow/pydot/setup.py b/bin/addons/base/ir/workflow/pydot/setup.py
new file mode 100755
index 00000000000..cd8956bebbd
--- /dev/null
+++ b/bin/addons/base/ir/workflow/pydot/setup.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+from distutils.core import setup
+import pydot
+
+setup( name = 'pydot',
+ version = pydot.__version__,
+ description = 'Python interface to Graphiz\'s Dot',
+ author = 'Ero Carrera',
+ author_email = 'ero@dkbza.org',
+ url = 'http://dkbza.org/pydot.html',
+ license = 'MIT',
+ platforms = ["any"],
+ classifiers = ['Development Status :: 5 - Production/Stable', \
+ 'Intended Audience :: Science/Research', \
+ 'License :: OSI Approved :: MIT License',\
+ 'Natural Language :: English', \
+ 'Operating System :: OS Independent', \
+ 'Programming Language :: Python', \
+ 'Topic :: Scientific/Engineering :: Visualization',\
+ 'Topic :: Software Development :: Libraries :: Python Modules'],
+ long_description = "\n".join(pydot.__doc__.split('\n')),
+ py_modules = ['pydot', 'dot_parser'])
diff --git a/bin/addons/base/ir/workflow/workflow.py b/bin/addons/base/ir/workflow/workflow.py
new file mode 100644
index 00000000000..ccecd477fde
--- /dev/null
+++ b/bin/addons/base/ir/workflow/workflow.py
@@ -0,0 +1,129 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# $Id: workflow.py 1304 2005-09-08 14:35:42Z nicoe $
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields, osv
+
+class workflow(osv.osv):
+ _name = "workflow"
+ _table = "wkf"
+ _log_access = False
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True),
+ 'osv': fields.char('Resource Model', size=64, required=True),
+ 'on_create': fields.boolean('On Create'),
+ 'activities': fields.one2many('workflow.activity', 'wkf_id', 'Activities'),
+ }
+ _defaults = {
+ 'on_create': lambda *a: True
+ }
+workflow()
+
+class wkf_activity(osv.osv):
+ _name = "workflow.activity"
+ _table = "wkf_activity"
+ _log_access = False
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True),
+ 'wkf_id': fields.many2one('workflow', 'Workflow', required=True, select=True),
+ 'split_mode': fields.selection([('XOR', 'Xor'), ('OR','Or'), ('AND','And')], 'Split Mode', size=3, required=True),
+ 'join_mode': fields.selection([('XOR', 'Xor'), ('AND', 'And')], 'Join Mode', size=3, required=True),
+ 'kind': fields.selection([('dummy', 'Dummy'), ('function', 'Function'), ('subflow', 'Subflow'), ('stopall', 'Stop All')], 'Kind', size=64, required=True),
+ 'action': fields.char('Action', size=64),
+ 'flow_start': fields.boolean('Flow Start'),
+ 'flow_stop': fields.boolean('Flow Stop'),
+ 'subflow_id': fields.many2one('workflow', 'Subflow'),
+ 'signal_send': fields.char('Signal (subflow.*)', size=32),
+ 'out_transitions': fields.one2many('workflow.transition', 'act_from', 'Outgoing transitions'),
+ 'in_transitions': fields.one2many('workflow.transition', 'act_to', 'Incoming transitions'),
+ }
+ _defaults = {
+ 'kind': lambda *a: 'dummy',
+ 'join_mode': lambda *a: 'XOR',
+ 'split_mode': lambda *a: 'XOR',
+ }
+wkf_activity()
+
+class wkf_transition(osv.osv):
+ _table = "wkf_transition"
+ _name = "workflow.transition"
+ _log_access = False
+ _rec_name = 'signal' #TODO: pas top mais bon...
+ _columns = {
+ 'trigger_model': fields.char('Trigger Type', size=128),
+ 'trigger_expr_id': fields.char('Trigger Expr ID', size=128),
+ 'signal': fields.char('Signal (button Name)', size=64),
+ 'role_id': fields.many2one('res.roles', 'Role Required'),
+ 'condition': fields.char('Condition', required=True, size=128),
+ 'act_from': fields.many2one('workflow.activity', 'Source Activity', required=True, select=True),
+ 'act_to': fields.many2one('workflow.activity', 'Destination Activity', required=True, select=True),
+ }
+ _defaults = {
+ 'condition': lambda *a: 'True',
+ }
+wkf_transition()
+
+class wkf_instance(osv.osv):
+ _table = "wkf_instance"
+ _name = "workflow.instance"
+ _rec_name = 'res_type'
+ _log_access = False
+ _columns = {
+ 'wkf_id': fields.many2one('workflow', 'Workflow', ondelete="cascade"),
+ 'uid': fields.integer('User ID'),
+ 'res_id': fields.integer('Resource ID'),
+ 'res_type': fields.char('Resource Model', size=64),
+ 'state': fields.char('State', size=32),
+ }
+wkf_instance()
+
+class wkf_workitem(osv.osv):
+ _table = "wkf_workitem"
+ _name = "workflow.workitem"
+ _log_access = False
+ _rec_name = 'state'
+ _columns = {
+ 'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="cascade"),
+ 'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="cascade"),
+ 'inst_id': fields.many2one('workflow.instance', 'Instance', required=True, ondelete="cascade"),
+ 'state': fields.char('State', size=64),
+ }
+wkf_workitem()
+
+class wkf_triggers(osv.osv):
+ _table = "wkf_triggers"
+ _name = "workflow.triggers"
+ _log_access = False
+ _columns = {
+ 'res_id': fields.integer('Resource ID', size=128),
+ 'model': fields.char('Model', size=128),
+ 'instance_id': fields.many2one('workflow.instance', 'Destination Instance', ondelete="cascade"),
+ 'workitem_id': fields.many2one('workflow.workitem', 'Workitem', required=True, ondelete="cascade"),
+ }
+wkf_triggers()
+
diff --git a/bin/addons/base/ir/workflow/workflow_print.py b/bin/addons/base/ir/workflow/workflow_print.py
new file mode 100644
index 00000000000..ba525cc63a8
--- /dev/null
+++ b/bin/addons/base/ir/workflow/workflow_print.py
@@ -0,0 +1,68 @@
+import psycopg
+import pydot
+
+wkf_id = 193
+
+db = psycopg.connect('dbname=terp', serialize=0)
+cr=db.cursor()
+
+def graph_get(cr, graph, wkf_id, nested=True):
+ cr.execute('select * from wkf_activity where wkf_id=%d', (wkf_id,))
+ nodes = cr.dictfetchall()
+ activities = {}
+ actfrom = {}
+ actto = {}
+ for n in nodes:
+ activities[n['id']] = n
+ if n['subflow_id'] and nested:
+ cr.execute('select * from wkf where id=%d', (n['subflow_id'],))
+ wkfinfo = cr.dictfetchone()
+ graph2 = pydot.Cluster('subflow'+str(n['subflow_id']), fontsize=10, label = "Subflow: "+n['name']+'\\nOSV: '+wkfinfo['osv'])
+ (s1,s2) = graph_get(cr, graph2, n['subflow_id'], nested)
+ graph.add_subgraph(graph2)
+ actfrom[n['id']] = s2
+ actto[n['id']] = s1
+ else:
+ args = {}
+ if n['flow_start'] or n['flow_stop']:
+ args['style']='filled'
+ args['color']='lightgrey'
+ args['label']=n['name']
+ if n['action']:
+ args['label']+='\\n'+n['action']
+ if n['subflow_id']:
+ args['shape'] = 'box'
+ graph.add_node(pydot.Node(n['id'], **args))
+ actfrom[n['id']] = n['id']
+ actto[n['id']] = n['id']
+ cr.execute('select * from wkf_transition where act_from in ('+','.join(map(lambda x: str(x['id']),nodes))+')')
+ transitions = cr.dictfetchall()
+ for t in transitions:
+ args = {}
+ args['label'] = str(t['condition'])
+ if t['signal']:
+ args['label'] += '\\n'+str(t['signal'])
+ args['style'] = 'bold'
+
+ if activities[t['act_from']]['split_mode']=='AND':
+ args['arrowtail']='box'
+ elif str(activities[t['act_from']]['split_mode'])=='OR ':
+ args['arrowtail']='inv'
+
+ if activities[t['act_to']]['join_mode']=='AND':
+ args['arrowhead']='crow'
+
+ graph.add_edge(pydot.Edge(actfrom[t['act_from']],actto[t['act_to']], fontsize=8, **args))
+ nodes = cr.dictfetchall()
+ cr.execute('select id from wkf_activity where flow_start=True limit 1')
+ start = cr.fetchone()[0]
+ cr.execute('select id from wkf_activity where flow_stop=True limit 1')
+ stop = cr.fetchone()[0]
+ return (start,stop)
+
+cr.execute('select * from wkf where id=%d', (wkf_id,))
+wkfinfo = cr.dictfetchone()
+graph = pydot.Dot(fontsize = 16, label = "\\n\\nWorkflow: %s\\n OSV: %s"% (wkfinfo['name'],wkfinfo['osv']))
+graph_get(cr, graph, wkf_id, True)
+graph.write_ps('/tmp/a.ps', prog='dot')
+
diff --git a/bin/addons/base/ir/workflow/workflow_view.xml b/bin/addons/base/ir/workflow/workflow_view.xml
new file mode 100644
index 00000000000..2a7de56c119
--- /dev/null
+++ b/bin/addons/base/ir/workflow/workflow_view.xml
@@ -0,0 +1,209 @@
+<?xml version="1.0"?>
+<terp>
+<data>
+ <menuitem name="Administration/Custom/Low level/Base/Workflows"/>
+
+ <!--
+ ================================
+ Workflows
+ ================================
+ -->
+
+ <record model="ir.ui.view" id="view_workflow_form">
+ <field name="name">workflow.form</field>
+ <field name="model">workflow</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Workflow">
+ <field name="name" select="1"/>
+ <field name="osv" select="1"/>
+ <field name="on_create"/>
+ <separator string="Activities" colspan="4"/>
+ <field name="activities" colspan="4" nolabel="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_workflow_form">
+ <field name="name">workflow.form</field>
+ <field name="res_model">workflow</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_workflow_form"/>
+ </record>
+ <menuitem
+ name="Administration/Custom/Low level/Base/Workflows/Workflows"
+ action="action_workflow_form"
+ id="menu_workflow"/>
+
+ <!--
+ ================================
+ Activities
+ ================================
+ -->
+
+ <record model="ir.ui.view" id="view_workflow_activity_form">
+ <field name="name">workflow.activity.form</field>
+ <field name="model">workflow.activity</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Activity">
+ <field name="name" colspan="3" select="1"/>
+ <field name="kind" select="1"/>
+ <field name="action" colspan="3" select="1"/>
+ <field name="subflow_id"/>
+ <field name="signal_send"/>
+ <newline/>
+ <field name="flow_start"/>
+ <field name="flow_stop"/>
+ <field name="split_mode"/>
+ <field name="join_mode"/>
+ <separator string="Outgoing transitions" colspan="4"/>
+ <field name="out_transitions" colspan="4" nolabel="1">
+ <tree string="Transitions">
+ <field name="act_to"/>
+ <field name="signal"/>
+ <field name="role_id"/>
+ <field name="condition"/>
+ <field name="trigger_model"/>
+ <field name="trigger_expr_id"/>
+ </tree>
+ </field>
+ <separator string="Incoming transitions" colspan="4"/>
+ <field name="in_transitions" colspan="4" nolabel="1">
+ <tree string="Transitions">
+ <field name="act_from"/>
+ <field name="signal"/>
+ <field name="role_id"/>
+ <field name="condition"/>
+ <field name="trigger_model"/>
+ <field name="trigger_expr_id"/>
+ </tree>
+ </field>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_workflow_activity_form">
+ <field name="name">workflow.activity.form</field>
+ <field name="res_model">workflow.activity</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_workflow_activity_form"/>
+ </record>
+ <menuitem
+ name="Administration/Custom/Low level/Base/Workflows/Activities"
+ action="action_workflow_activity_form"
+ id="menu_workflow_activity"/>
+
+
+ <!--
+ ================================
+ Transitions
+ ================================
+ -->
+
+ <record model="ir.ui.view" id="view_workflow_transition_form">
+ <field name="name">workflow.transition.form</field>
+ <field name="model">workflow.transition</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Transition">
+ <field name="act_from" select="1"/>
+ <field name="act_to" select="1"/>
+ <field name="condition" colspan="3"/>
+ <field name="signal"/>
+ <field name="role_id"/>
+ <field name="trigger_model"/>
+ <field name="trigger_expr_id"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_workflow_transition_tree">
+ <field name="name">workflow.transition.tree</field>
+ <field name="model">workflow.transition</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Transition">
+ <field name="act_from"/>
+ <field name="act_to"/>
+ <field name="signal"/>
+ <field name="role_id"/>
+ <field name="condition"/>
+ <field name="trigger_model"/>
+ <field name="trigger_expr_id"/>
+ </tree>
+ </field>
+ </record>
+
+
+ <record model="ir.actions.act_window" id="action_workflow_transition_form">
+ <field name="name">workflow.transition.form</field>
+ <field name="res_model">workflow.transition</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_workflow_transition_form"/>
+ </record>
+ <menuitem
+ name="Administration/Custom/Low level/Base/Workflows/Transitions"
+ action="action_workflow_transition_form"
+ id="menu_workflow_transition"/>
+
+ <!--
+ ================================
+ Instances
+ ================================
+ -->
+
+ <record model="ir.ui.view" id="view_workflow_instance_form">
+ <field name="name">workflow.instance.form</field>
+ <field name="model">workflow.instance</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Workflow Instances">
+ <field name="wkf_id" select="1"/>
+ <field name="uid" select="1"/>
+ <field name="res_id" select="1"/>
+ <field name="res_type" select="1"/>
+ <field name="state" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_workflow_instance_form">
+ <field name="name">workflow.instance.form</field>
+ <field name="res_model">workflow.instance</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_workflow_instance_form"/>
+ </record>
+ <menuitem
+ name="Administration/Custom/Low level/Base/Workflows/Instances"
+ action="action_workflow_instance_form"
+ id="menu_workflow_instance"/>
+
+ <!--
+ ================================
+ Workitems
+ ================================
+ -->
+
+ <record model="ir.ui.view" id="view_workflow_workitem_form">
+ <field name="name">workflow.workitem.form</field>
+ <field name="model">workflow.workitem</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Workflow Workitems">
+ <field name="act_id" select="1"/>
+ <field name="subflow_id" select="1"/>
+ <field name="inst_id" select="1"/>
+ <field name="state" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_workflow_workitem_form">
+ <field name="name">workflow.workitem.form</field>
+ <field name="res_model">workflow.workitem</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_workflow_workitem_form"/>
+ </record>
+ <menuitem
+ name="Administration/Custom/Low level/Base/Workflows/Workitems"
+ action="action_workflow_workitem_form"
+ id="menu_workflow_workitem"/>
+
+</data>
+</terp>
diff --git a/bin/addons/base/module/__init__.py b/bin/addons/base/module/__init__.py
new file mode 100644
index 00000000000..bc74f4ed9a6
--- /dev/null
+++ b/bin/addons/base/module/__init__.py
@@ -0,0 +1,31 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import module
+import wizard
+import report
diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py
new file mode 100644
index 00000000000..bc9aefc0a32
--- /dev/null
+++ b/bin/addons/base/module/module.py
@@ -0,0 +1,274 @@
+##############################################################################
+#
+# Copyright (c) 2005 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import tarfile
+import re
+import urllib
+import os
+import tools
+from osv import fields, osv
+
+class module_repository(osv.osv):
+ _name = "ir.module.repository"
+ _description = "Module Repository"
+ _columns = {
+ 'name': fields.char('Name', size=128),
+ 'url': fields.char('Url', size=256, required=True),
+ }
+module_repository()
+
+def get_module_info(name):
+ try:
+ f = file(os.path.join(tools.config['addons_path'], name, '__terp__.py'), 'r')
+ data = f.read()
+ info = eval(data)
+ f.close()
+ except:
+ return {}
+ return info
+
+class module_category(osv.osv):
+ _name = "ir.module.category"
+ _description = "Module Category"
+
+ def _module_nbr(self,cr,uid, ids, prop, unknow_none,context):
+ cr.execute('select category_id,count(*) from ir_module_module where category_id in ('+','.join(map(str,ids))+') or category_id in (select id from ir_module_category where parent_id in ('+','.join(map(str,ids))+')) group by category_id')
+ result = dict(cr.fetchall())
+ for id in ids:
+ cr.execute('select id from ir_module_category where parent_id=%d', (id,))
+ childs = [c for c, in cr.fetchall()]
+ result[id] = reduce(lambda x,y:x+y, [result.get(c, 0) for c in childs], result.get(id, 0))
+ return result
+
+ _columns = {
+ 'name': fields.char("Name", size=128, required=True),
+ 'parent_id': fields.many2one('ir.module.category', 'Parent Category', select=True),
+ 'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Parent Category'),
+ 'module_nr': fields.function(_module_nbr, method=True, string='# of Modules', type='integer')
+ }
+ _order = 'name'
+module_category()
+
+class module(osv.osv):
+ _name = "ir.module.module"
+ _description = "Module"
+
+ def _get_installed_version(self, cr, uid, ids, field_name=None, arg=None, context={}):
+ res = {}
+ for m in self.browse(cr, uid, ids):
+ res[m.id] = get_module_info(m.name).get('version', False)
+ return res
+
+ _columns = {
+ 'name': fields.char("Name", size=128, readonly=True, required=True),
+ 'category_id': fields.many2one('ir.module.category', 'Category', readonly=True),
+ 'shortdesc': fields.char('Short description', size=256, readonly=True),
+ 'description': fields.text("Description", readonly=True),
+ 'author': fields.char("Author", size=128, readonly=True),
+ 'website': fields.char("Website", size=256, readonly=True),
+ 'installed_version': fields.function(_get_installed_version, method=True, string='Installed version', type='char'),
+ 'latest_version': fields.char('Latest version', size=64, readonly=True),
+ 'url': fields.char('URL', size=128, readonly=True),
+ 'dependencies_id': fields.one2many('ir.module.module.dependency', 'module_id', 'Dependencies'),
+ 'state': fields.selection([
+ ('uninstallable','Uninstallable'),
+ ('uninstalled','Not Installed'),
+ ('installed','Installed'),
+ ('to upgrade','To be upgraded'),
+ ('to remove','To be removed'),
+ ('to install','To be installed')
+ ], string='State', readonly=True),
+ 'demo': fields.boolean('Demo data'),
+ }
+
+ _defaults = {
+ 'state': lambda *a: 'uninstalled',
+ 'demo': lambda *a: False,
+ }
+ _order = 'name'
+
+ def state_change(self, cr, uid, ids, newstate, context={}, level=50):
+ if level<1:
+ raise 'Recursion error in modules dependencies !'
+ ids_dest = []
+ for module in self.browse(cr, uid, ids):
+ for dep in module.dependencies_id:
+ ids2 = self.search(cr, uid, [('name','=',dep.name)])
+ for id in ids2:
+ if (id not in ids_dest):
+ ids_dest.append(id)
+ if module.state=='uninstalled':
+ self.write(cr, uid, [module.id], {'state': newstate})
+ if ids_dest:
+ self.state_change(cr, uid, ids_dest, newstate, context, level-1)
+
+ for module in self.browse(cr, uid, ids):
+ if module.state!='installed':
+ demo=True
+ for dep in module.dependencies_id:
+ ids2 = self.search(cr, uid, [('name', '=', dep.name)])
+ for module2 in self.browse(cr, uid, ids2):
+ demo = demo and module2.demo
+ self.write(cr, uid, [module.id], {'demo':demo})
+ return True
+
+ def button_install(self, cr, uid, ids, context={}):
+ return self.state_change(cr, uid, ids, 'to install', context)
+
+ def button_install_cancel(self, cr, uid, ids, context={}):
+ self.write(cr, uid, ids, {'state': 'uninstalled'})
+ return True
+
+ def button_uninstall(self, cr, uid, ids, context={}):
+ self.write(cr, uid, ids, {'state': 'to remove'})
+ return True
+
+ def button_remove_cancel(self, cr, uid, ids, context={}):
+ self.write(cr, uid, ids, {'state': 'installed'})
+ return True
+ def button_upgrade(self, cr, uid, ids, context={}):
+ self.write(cr, uid, ids, {'state': 'to upgrade'})
+ return True
+ def button_upgrade_cancel(self, cr, uid, ids, context={}):
+ self.write(cr, uid, ids, {'state': 'installed'})
+ return True
+ def button_update_translations(self, cr, uid, ids, context={}):
+ cr.execute('select code from res_lang where translatable=TRUE')
+ langs = [l[0] for l in cr.fetchall()]
+ modules = self.read(cr, uid, ids, ['name'])
+ for module in modules:
+ files = get_module_info(module['name']).get('translations', {})
+ for lang in langs:
+ if files.has_key(lang):
+ filepath = files[lang]
+ # if filepath does not contain :// we prepend the path of the module
+ if filepath.find('://') == -1:
+ filepath = os.path.join(tools.config['addons_path'], module['name'], filepath)
+ tools.trans_load(filepath, lang)
+ return True
+
+ # update the list of available packages
+ def update_list(self, cr, uid, context={}):
+ robj = self.pool.get('ir.module.repository')
+ adp = tools.config['addons_path']
+
+ # iterate through installed modules and mark them as being so
+ for name in os.listdir(adp):
+ if os.path.isdir(os.path.join(adp, name)):
+ version = get_module_info(name).get('version', False)
+ if version:
+ ids = self.search(cr, uid, [('name','=',name)])
+ if not ids:
+ id = self.create(cr, uid, {
+ 'name': name,
+ 'latest_version': version,
+ 'state': 'installed',
+ })
+ else:
+ self.write(cr, uid, ids, {'state': 'installed'})
+
+ # make the list of all installable modules
+ for repository in robj.browse(cr, uid, robj.search(cr, uid, [])):
+ index_page = urllib.urlopen(repository.url).read()
+ modules = re.findall('.*<a href="([a-zA-Z0-9.\-]+)_([a-zA-Z0-9.\-]+)\.tar\.gz">.*', index_page)
+ for name, version in modules:
+ # TODO: change this using urllib
+ url = os.path.join(repository.url, name + '_' + version + ".tar.gz")
+ ids = self.search(cr, uid, [('name','=',name)])
+ if not ids:
+ self.create(cr, uid, {
+ 'name': name,
+ 'latest_version': version,
+ 'url': url,
+ 'state': 'uninstalled',
+ })
+ else:
+ for r in self.read(cr, uid, ids, ['latest_version']):
+ if r['latest_version'] < version:
+ self.write(cr, uid, [r['id']], {'latest_version': version, 'url':url})
+ return True
+
+ #
+ # TODO: update dependencies
+ #
+ def info_get(self, cr, uid, ids, context={}):
+ categ_obj = self.pool.get('ir.module.category')
+
+ for module in self.browse(cr, uid, ids, context):
+ url = module.url
+ adp = tools.config['addons_path']
+ info = False
+ if url:
+ tar = tarfile.open(mode="r|gz", fileobj=urllib.urlopen(url))
+ for tarinfo in tar:
+ if tarinfo.name.endswith('__terp__.py'):
+ info = eval(tar.extractfile(tarinfo).read())
+ elif os.path.isdir(os.path.join(adp, module.name)):
+ info = get_module_info(module.name)
+ if info:
+ categ = info.get('category', 'Unclassified')
+ parent = False
+ for c in categ.split('/'):
+ ids = categ_obj.search(cr, uid, [('name','=',c), ('parent_id','=',parent)])
+ if not ids:
+ parent = categ_obj.create(cr, uid, {'name':c, 'parent_id':parent})
+ else:
+ parent = ids[0]
+ self.write(cr, uid, [module.id], {
+ 'author': info.get('author',False),
+ 'website': info.get('website',False),
+ 'shortdesc': info.get('name',False),
+ 'description': info.get('description',False),
+ 'category_id': parent
+ })
+ return True
+module()
+
+class module_dependency(osv.osv):
+ _name = "ir.module.module.dependency"
+ _description = "Module dependency"
+ _columns = {
+ 'name': fields.char('Name', size=128),
+ 'module_id': fields.many2one('ir.module.module', 'Module', select=True),
+ #'module_dest_id': fields.many2one('ir.module.module', 'Module'),
+ 'version_pattern': fields.char('Required Version', size=128),
+ }
+ # returns the ids of module version records which match all dependencies
+ # [version_id, ...]
+ def resolve(self, cr, uid, ids):
+ vobj = self.pool.get('ir.module.module.version')
+ objs = self.browse(cr, uid, ids)
+ res = {}
+ for o in objs:
+ pattern = o.version_pattern and eval(o.version_pattern) or []
+ print "pattern", pattern
+ res[o.id] = vobj.search(cr, uid, [('module','=',o.module.id)]+pattern)
+ #TODO: add smart dependencies resolver here
+ # it should compute the best version for each module
+ return [r[0] for r in res.itervalues()]
+module_dependency()
+
diff --git a/bin/addons/base/module/module.py.old b/bin/addons/base/module/module.py.old
new file mode 100644
index 00000000000..7f1512f7c03
--- /dev/null
+++ b/bin/addons/base/module/module.py.old
@@ -0,0 +1,228 @@
+##############################################################################
+#
+# Copyright (c) 2005 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import tarfile
+import re
+import urllib2
+import os
+import shutil
+from sets import Set
+
+from osv import fields, osv
+import tools
+
+class module(osv.osv):
+ _name = "module.module"
+ _description = "Module"
+
+ def _costs_get(self, cr, uid, ids, prop=None, unknown_none=None, unknown_dict={}):
+ dossiers = self.browse(cr, uid, ids)
+ res = {}
+ for d in dossiers:
+ costs = [l.amount_costs for l in d.lot_id]
+ cost_amount = reduce(lambda x, y: x+y, costs, 0.0)
+ res[d.id] = cost_amount
+ return res
+
+ _columns = {
+ 'name': fields.char("Name", size=128, readonly=True),
+ 'shortdesc': fields.char('Short description', size=256, readonly=True),
+ 'description': fields.text("Description", readonly=True),
+ 'author': fields.char("Author", size=128, readonly=True),
+ 'website': fields.char("Website", size=256, readonly=True),
+# 'running_version': fields.function(_get_installed_version, method=True, string='Installed version'),
+# 'installed_version': fields.function(_get_installed_version, method=True, string='Installed version'),
+ 'latest_version': fields.many2one('module.module.version', 'Latest version'),
+ 'versions': fields.one2many('module.module.version', 'module', 'Versions', readonly=True),
+ 'state': fields.selection([('uninstalled', 'Uninstalled'), ('preinstall','To install'),('installed','Installed'),('running','Running')], 'State'),
+# 'active': fields.boolean('Active'),
+ }
+
+ # update the list of available packages
+ def update(self, cr, uid, ids, *args):
+ vobj = self.pool.get('module.module.version')
+
+ # update installed_version
+
+ # get the index page containing available packages from the server
+ index_page = urllib2.urlopen('http://www.tinyerp.org/download/modules_test').read()
+# print index_page
+ # parses it
+ modules = re.findall('.*<a href="([a-zA-Z0-9.\-]+)_([a-zA-Z0-9.\-]+)\.tar\.gz">.*', index_page)
+ # create module.module records and module.module.version as needed
+ for name, version in modules:
+ print "name", name, "version", version
+ # open module package on the remote server and extract its __terp__.py
+ url = 'http://www.tinyerp.org/download/modules_test/' + name + '_' + version + ".tar.gz"
+ tar = tarfile.open(mode="r|gz", fileobj=urllib2.urlopen(url))
+ info = {}
+ # we need to go through the whole tar file, because we use a stream and we
+ # are not allowed to search backward in the stream, so we can't extract one
+ # particular file directly
+ for tarinfo in tar:
+ if tarinfo.name.endswith('__terp__.py'):
+ info = eval(tar.extractfile(tarinfo).read())
+ break
+ tar.close()
+ print info
+ ids = self.search(cr, uid, [('name','=',name)])
+ print ids
+ if not ids:
+ id = self.create(cr, uid, {
+ 'name': name,
+ 'shortdesc': info.get('name', False),
+ 'description': info.get('description', False),
+ 'author': info.get('author', False),
+ 'website': info.get('website', False),
+ 'state': 'uninstalled',
+ })
+ print "module_id", id
+ else:
+ assert len(ids)==1, "There shouldn't be two modules with the same name"
+ id = ids[0]
+
+ version_ids = vobj.search(cr, uid, [('module','=',id),('name','=',version)])
+ print version_ids
+ if not version_ids:
+ version_id = vobj.create(cr, uid, {
+ 'name': version,
+ 'module': id,
+ 'state': 'uninstalled',
+ })
+ print "version_id", version_id
+ # update latest_version
+ #TODO: compute latest version
+ self.write(cr, uid, [id], {'latest_version': version_id})
+# else:
+# assert len(version_ids)==1, "There shouldn't be two versions with the same name"
+# version_id = version_ids[0]
+
+ return True
+
+ def install(self, cr, uid, ids, *args):
+ objs = self.browse(cr, uid, ids)
+ vobj = self.pool.get('module.module.version')
+
+ # get the id of latest version for each module
+ version_ids = Set([o.latest_version.id for o in objs])
+# for o in objs:
+# version_ids.add()
+# version_ids = reduce(lambda dic, o: dic.update({o.latest_version.id:True}), objs, {})
+ print "version_ids", version_ids
+
+ # add the list of dependencies
+ dependencies_ids = vobj.get_dependencies(cr, uid, list(version_ids))
+ print "depends", dependencies_ids
+ version_ids.update(dependencies_ids)
+# version_ids = reduce(lambda dic, dep: dic.update({dep:True}), dependencies_ids, version_ids)
+ print "version_ids2", version_ids
+
+ # remove existing version of modules
+ self.remove(cr, uid, ids)
+
+ # install all selected modules and their dependencies
+ vobj.install(cr, uid, list(version_ids))
+ return True
+
+
+ # remove existing version of modules if they exist
+ def remove(self, cr, uid, ids, *args):
+ objs = self.browse(cr, uid, ids)
+ adp = tools.config['addons_path']
+ addons = os.listdir(adp)
+ for o in objs:
+ if o.name in addons:
+ shutil.rmtree(os.path.join(adp, o.name))
+ return True
+module()
+
+class module_version(osv.osv):
+ _name = "module.module.version"
+ _description = "Module Version"
+ _columns = {
+ 'name': fields.char('Name', size=64),
+ 'module': fields.many2one('module.module', "Module"),
+ 'dependencies': fields.one2many('module.module.dependency', 'version', 'Dependencies'),
+ 'state': fields.selection([('uninstalled','Uninstalled'), ('preinstall','To install'), ('installed','Installed'), ('running','Running')], 'State'),
+ }
+
+ def install(self, cr, uid, ids, *args):
+ print "install versions", ids
+ objs = self.browse(cr, uid, ids)
+
+ for o in objs:
+ # download and unpack to destination folder
+ url = 'http://www.tinyerp.org/download/modules_test/' + o.module.name + '_' + o.name + ".tar.gz"
+ tar = tarfile.open(mode="r|gz", fileobj=urllib2.urlopen(url))
+ for tarinfo in tar:
+ tar.extract(tarinfo, tools.config['addons_path'])
+ return True
+
+ def get_dependencies(self, cr, uid, ids, *args):
+ dobj = self.pool.get('module.module.dependency')
+ print "get_depends", ids
+ # for each dependency, get dependencies
+ objs = self.browse(cr, uid, ids)
+ depends = []
+ for o in objs:
+ print "name", o.name
+ o_depends = dobj.resolve(cr, uid, [d.id for d in o.dependencies])
+ print "depends", o_depends
+ depends.extend(o_depends)
+
+# depends.extend([d.id for d in o.dependencies])
+ print "total depends", depends
+ # merge the list
+ # return the list of ids
+ return depends
+module_version()
+
+# a module dependency record represents one dependency of a particular version of a module
+# it can depend on a range of version of one module
+class module_dependency(osv.osv):
+ _name = "module.module.dependency"
+ _description = "Module dependency"
+ _columns = {
+ 'dependency_for': fields.many2one('module.module.version', 'Version'),
+ 'module': fields.many2one('module.module', 'Module'),
+ 'version_pattern': fields.char('Version pattern', size=128),
+ }
+
+ # returns the ids of module version records which match all dependencies
+ # [version_id, ...]
+ def resolve(self, cr, uid, ids):
+ vobj = self.pool.get('module.module.version')
+ objs = self.browse(cr, uid, ids)
+ res = {}
+ for o in objs:
+ pattern = o.version_pattern and eval(o.version_pattern) or []
+ print "pattern", pattern
+ res[o.id] = vobj.search(cr, uid, [('module','=',o.module.id)]+pattern)
+ #TODO: add smart dependencies resolver here
+ # it should compute the best version for each module
+ return [r[0] for r in res.itervalues()]
+module_dependency()
diff --git a/bin/addons/base/module/module_data.xml b/bin/addons/base/module/module_data.xml
new file mode 100644
index 00000000000..930ebeefc8d
--- /dev/null
+++ b/bin/addons/base/module/module_data.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<terp>
+ <data>
+ <!--
+ <record model="ir.module.repository" id="module_repository_tiny">
+ <field name="name">Tiny ERP Base Repository</field>
+ <field name="url">http://tinyerp.org/download/modules/</field>
+ </record>
+ -->
+ </data>
+</terp>
+
diff --git a/bin/addons/base/module/module_report.xml b/bin/addons/base/module/module_report.xml
new file mode 100644
index 00000000000..f76dc02d21a
--- /dev/null
+++ b/bin/addons/base/module/module_report.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" ?>
+<terp>
+ <data>
+
+ <report id="ir_module_reference_print"
+ string="Technical guide"
+ model="ir.module.module"
+ name="ir.module.reference"
+ rml="base/module/report/ir_module_reference.rml"
+ auto="False" />
+
+ </data>
+</terp>
diff --git a/bin/addons/base/module/module_view.xml b/bin/addons/base/module/module_view.xml
new file mode 100644
index 00000000000..29f1d867d9c
--- /dev/null
+++ b/bin/addons/base/module/module_view.xml
@@ -0,0 +1,198 @@
+<?xml version="1.0" ?>
+<terp>
+ <data>
+
+ #
+ # Modules Categories
+ #
+
+ <record model="ir.ui.view" id="view_module_category_form">
+ <field name="name">ir.module.category.form</field>
+ <field name="model">ir.module.category</field>
+ <field name="type">form</field>
+ <field name="field_parent">child_ids</field>
+ <field name="arch" type="xml">
+ <form string="Module Category">
+ <field name="name" colspan="3"/>
+ <field name="parent_id" colspan="3"/>
+ </form>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="view_module_category_tree">
+ <field name="name">ir.module.category.tree</field>
+ <field name="model">ir.module.category</field>
+ <field name="type">tree</field>
+ <field name="field_parent">child_ids</field>
+ <field name="arch" type="xml">
+ <tree string="Module Category">
+ <field name="name"/>
+ <field name="module_nr"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_module_category_tree">
+ <field name="name">ir.module.category</field>
+ <field name="res_model">ir.module.category</field>
+ <field name="view_type">tree</field>
+ <field name="domain">[('parent_id','=',False)]</field>
+ </record>
+ <menuitem
+ name="Administration/Modules Management/Categories of Modules"
+ action="action_module_category_tree"
+ />
+
+ #
+ # Click on a category
+ #
+
+ <record model="ir.actions.act_window" id="action_module_open_categ">
+ <field name="name">ir.module.module</field>
+ <field name="res_model">ir.module.module</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="domain">[('category_id','=',active_id)]</field>
+ </record>
+ <record model="ir.values" id="ir_action_module_category">
+ <field name="key2" eval="'tree_but_open'"/>
+ <field name="model" eval="'ir.module.category'"/>
+ <field name="name">Categorized Modules</field>
+ <field name="value" eval="'ir.actions.act_window,%d'%action_module_open_categ"/>
+ <field name="object" eval="True"/>
+ </record>
+
+
+ <!-- Modules -->
+
+ <record model="ir.ui.view" id="module_form">
+ <field name="name">ir.module.module.form</field>
+ <field name="model">ir.module.module</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Module">
+ <notebook>
+ <page string="Module">
+ <field name="name" select="1"/>
+ <field name="shortdesc" colspan="3"/>
+ <field name="description" colspan="3"/>
+ <field name="installed_version"/>
+ <field name="latest_version"/>
+ <field name="author"/>
+ <field name="website"/>
+ <field name="url"/>
+ <field name="demo" readonly="1"/>
+ <field name="state" readonly="1"/>
+ <group col="7" colspan="2">
+ <button string="Update Infos" name="info_get" type="object" states="uninstalled,installed,to upgrade,to remove"/>
+ <button string="Cancel Install" name="button_install_cancel" type="object" states="to install"/>
+ <button string="Install" name="button_install" type="object" states="uninstalled"/>
+ <!--
+ <button string="Cancel Upgrade" name="button_upgrade_cancel" type="object" states="to upgrade"/>
+ <button string="Cancel Remove" name="button_remove_cancel" type="object" states="to remove"/>
+ <button string="Uninstall" name="button_uninstall" type="object" states="installed"/>
+ -->
+ <button string="Upgrade" name="button_upgrade" type="object" states="installed"/>
+ <!--
+ <button string="Update translations" name="button_update_translations" type="object" states="installed"/>
+ -->
+ </group>
+ </page>
+ <page string="Dependencies">
+ <field name="dependencies_id" colspan="4" nolabel="1">
+ <tree string="Dependencies">
+ <field name="name"/>
+ <field name="version_pattern"/>
+ </tree>
+ </field>
+ </page>
+ </notebook>
+ </form>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="module_tree">
+ <field name="name">ir.module.module.tree</field>
+ <field name="model">ir.module.module</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Module list" colors="blue:state=='to upgrade' or state=='to install';grey:state=='uninstalled' or state=='uninstallable'">
+ <field name="name"/>
+ <field name="shortdesc"/>
+ <field name="author"/>
+ <field name="installed_version"/>
+ <field name="latest_version"/>
+ <field name="state"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="open_module_tree">
+ <field name="name">ir.module.module</field>
+ <field name="res_model">ir.module.module</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="domain"></field>
+ </record>
+ <menuitem name="Administration/Modules Management/Modules" action="open_module_tree"/>
+
+ <record model="ir.actions.act_window" id="open_module_tree_install">
+ <field name="name">ir.module.module</field>
+ <field name="res_model">ir.module.module</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="domain">[('state','=','installed')]</field>
+ </record>
+ <menuitem name="Administration/Modules Management/Modules/Installed Modules" action="open_module_tree_install"/>
+ <record model="ir.actions.act_window" id="open_module_tree_uninstall">
+ <field name="name">ir.module.module</field>
+ <field name="res_model">ir.module.module</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="domain">[('state','=','uninstalled')]</field>
+ </record>
+ <menuitem name="Administration/Modules Management/Modules/Uninstalled Modules" action="open_module_tree_uninstall"/>
+ <record model="ir.actions.act_window" id="open_module_tree_upgrade">
+ <field name="name">ir.module.module</field>
+ <field name="res_model">ir.module.module</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="domain">[('state','&lt;&gt;','uninstalled'),('state','&lt;&gt;','installed'),('state','&lt;&gt;','uninstallable'),]</field>
+ </record>
+ <menuitem name="Administration/Modules Management/Modules/Modules to be installed, upgraded or removed" action="open_module_tree_upgrade"/>
+
+ <record model="ir.ui.view" id="repository_form">
+ <field name="name">ir.module.repository.form</field>
+ <field name="model">ir.module.repository</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Repository">
+ <field name="name" colspan="3" select="1"/>
+ <field name="url" colspan="3" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="repository_tree">
+ <field name="name">ir.module.repository.tree</field>
+ <field name="model">ir.module.repository</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Repository list">
+ <field name="name"/>
+ <field name="url"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="open_repository_tree">
+ <field name="name">ir.module.repository</field>
+ <field name="res_model">ir.module.repository</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <!--
+ <menuitem
+ name="Administration/Modules Management/Repositories"
+ action="open_repository_tree"
+ id="menu_module_repository_tree"
+ />
+ -->
+
+ </data>
+</terp>
diff --git a/bin/addons/base/module/module_wizard.xml b/bin/addons/base/module/module_wizard.xml
new file mode 100644
index 00000000000..4c6182a2055
--- /dev/null
+++ b/bin/addons/base/module/module_wizard.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<terp>
+ <data>
+ <wizard
+ id="wizard_info_get"
+ string="Request Info"
+ model="ir.module.module"
+ name="module.info_get"/>
+
+ <!--
+ <record model="ir.actions.wizard" id="wizard_update">
+ <field name="name">Download module list</field>
+ <field name="wiz_name">module.module.update</field>
+ </record>
+ <menuitem
+ name="Administration/Module Management/Download module list"
+ action="wizard_update"
+ type="wizard"
+ groups="admin"
+ icon="STOCK_CONVERT"
+ id="menu_module_update"/>
+ -->
+
+ <record model="ir.actions.wizard" id="wizard_upgrade">
+ <field name="name">Apply marked changes</field>
+ <field name="wiz_name">module.upgrade</field>
+ </record>
+ <menuitem
+ name="Administration/Modules Management/Apply Upgrades"
+ action="wizard_upgrade"
+ id="menu_wizard_upgrade"
+ type="wizard"/>
+
+ <record model="ir.actions.wizard" id="wizard_lang_install">
+ <field name="name">Install new language file</field>
+ <field name="wiz_name">module.lang.install</field>
+ </record>
+ <menuitem
+ name="Administration/Translations/Load a new language"
+ action="wizard_lang_install"
+ id="menu_wizard_lang_install"
+ type="wizard"/>
+
+ <record model="ir.actions.wizard" id="wizard_scan_addons">
+ <field name="name">Scan 'addons' path</field>
+ <field name="wiz_name">module.module.scan</field>
+ </record>
+ <menuitem
+ name="Administration/Modules Management/Update modules list"
+ action="wizard_scan_addons"
+ id="menu_wizard_module_scan"
+ type="wizard" />
+
+ <record model="ir.actions.wizard" id="wizard_lang_export">
+ <field name="name">Export a language</field>
+ <field name="wiz_name">module.lang.export</field>
+ </record>
+ <menuitem
+ name="Administration/Translations/Export a language"
+ action="wizard_lang_export"
+ id="menu_wizard_lang_export"
+ type="wizard"/>
+
+ <record model="ir.actions.wizard" id="wizard_lang_import">
+ <field name="name">Import a language</field>
+ <field name="wiz_name">module.lang.import</field>
+ </record>
+ <menuitem
+ name="Administration/Translations/Import a language"
+ action="wizard_lang_import"
+ id="menu_wizard_lang_import"
+ type="wizard"/>
+
+ </data>
+</terp>
+
diff --git a/bin/addons/base/module/report/__init__.py b/bin/addons/base/module/report/__init__.py
new file mode 100644
index 00000000000..49e3c07d385
--- /dev/null
+++ b/bin/addons/base/module/report/__init__.py
@@ -0,0 +1,29 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import ir_module_reference_print
diff --git a/bin/addons/base/module/report/ir_module_reference.rml b/bin/addons/base/module/report/ir_module_reference.rml
new file mode 100644
index 00000000000..ca1cef0f610
--- /dev/null
+++ b/bin/addons/base/module/report/ir_module_reference.rml
@@ -0,0 +1,173 @@
+<?xml version="1.0"?>
+<document filename="test.pdf">
+ <template pageSize="(595.0,842.0)" title="Test" author="Martin Simon" allowSplitting="20">
+ <pageTemplate id="first">
+ <frame id="first" x1="42.0" y1="42.0" width="511" height="758"/>
+ </pageTemplate>
+ </template>
+ <stylesheet>
+ <blockTableStyle id="Standard_Outline">
+ <blockAlignment value="LEFT"/>
+ <blockValign value="TOP"/>
+ </blockTableStyle>
+ <blockTableStyle id="Table1">
+ <blockAlignment value="LEFT"/>
+ <blockValign value="TOP"/>
+ <blockBackground colorName="#e6e6e6" start="0,0" stop="0,0"/>
+ <blockBackground colorName="#e6e6e6" start="1,0" stop="1,0"/>
+ <blockBackground colorName="#e6e6e6" start="2,0" stop="2,0"/>
+ <blockBackground colorName="#e6e6e6" start="0,1" stop="0,1"/>
+ <blockBackground colorName="#e6e6e6" start="1,1" stop="1,1"/>
+ <blockBackground colorName="#e6e6e6" start="2,1" stop="2,1"/>
+ </blockTableStyle>
+ <blockTableStyle id="Tableau1">
+ <blockAlignment value="LEFT"/>
+ <blockValign value="TOP"/>
+ <lineStyle kind="GRID" colorName="black"/>
+ <blockBackground colorName="#ff6633" start="0,0" stop="0,0"/>
+ </blockTableStyle>
+ <blockTableStyle id="Tableau2">
+ <blockAlignment value="LEFT"/>
+ <blockValign value="TOP"/>
+ <blockBackground colorName="#e6e6e6" start="0,0" stop="0,0"/>
+ <blockBackground colorName="#e6e6e6" start="1,0" stop="1,0"/>
+ <blockBackground colorName="#e6e6e6" start="0,1" stop="0,1"/>
+ <blockBackground colorName="#e6e6e6" start="1,1" stop="1,1"/>
+ </blockTableStyle>
+ <blockTableStyle id="Tableau3">
+ <blockAlignment value="LEFT"/>
+ <blockValign value="TOP"/>
+ <lineStyle kind="GRID" colorName="black"/>
+ <blockBackground colorName="#ffcc99" start="0,0" stop="0,0"/>
+ </blockTableStyle>
+ <blockTableStyle id="Tableau4">
+ <blockAlignment value="LEFT"/>
+ <blockValign value="TOP"/>
+ </blockTableStyle>
+ <initialize>
+ <paraStyle name="all" alignment="justify"/>
+ </initialize>
+ <paraStyle name="P1" fontName="Times-Roman" fontSize="20.0" leading="25" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="P2" fontName="Times-Roman" fontSize="10.0" leading="13" alignment="RIGHT" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="P3" fontName="Times-Roman" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="P4" fontName="Times-Roman" fontSize="11.0" leading="14" alignment="RIGHT" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="P5" fontName="Times-Roman" fontSize="16.0" leading="20" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="P6" fontName="Times-Roman"/>
+ <paraStyle name="P7" fontName="Times-Roman" fontSize="15.0" leading="19" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="P8" fontName="Times-Roman" fontSize="11.0" leading="14" alignment="LEFT" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="P9" fontName="Times-Roman" alignment="LEFT" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="Standard" fontName="Times-Roman"/>
+ <paraStyle name="Text body" fontName="Times-Roman" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="Heading" fontName="Helvetica" fontSize="14.0" leading="17" spaceBefore="12.0" spaceAfter="6.0"/>
+ <paraStyle name="List" fontName="Times-Roman" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="Table Contents" fontName="Times-Roman" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="Table Heading" fontName="Times-Roman" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/>
+ <paraStyle name="Caption" fontName="Times-Roman" fontSize="12.0" leading="15" spaceBefore="6.0" spaceAfter="6.0"/>
+ <paraStyle name="Index" fontName="Times-Roman"/>
+ </stylesheet>
+ <story>
+ <para style="Standard">
+ <font color="white"> </font>
+ </para>
+ <blockTable colWidths="139.0,220.0,152.0" repeatRows="1" style="Table1">
+ <tr>
+ <td>
+ <para style="Table Contents">
+ <font color="white"> </font>
+ </para>
+ </td>
+ <td>
+ <para style="P1">Reference Guide</para>
+ </td>
+ <td>
+ <para style="P2">
+ <font color="white"> </font>
+ </para>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <para style="Table Contents">[[ company.name ]]</para>
+ </td>
+ <td>
+ <para style="P3">Introspection report on objects</para>
+ </td>
+ <td>
+ <para style="P4">Printed: [[ time.strftime('%y-%m-%d')]]</para>
+ </td>
+ </tr>
+ </blockTable>
+ <para style="Standard">
+ <font color="white"> </font>
+ </para>
+ <section>
+ <para style="Text body">[[ repeatIn(objects, 'module') ]]</para>
+ <blockTable colWidths="510.0" repeatRows="1" style="Tableau1">
+ <tr>
+ <td>
+ <para style="P5">Module: [[ module.name ]]</para>
+ </td>
+ </tr>
+ </blockTable>
+ <blockTable colWidths="276.0,234.0" repeatRows="1" style="Tableau2">
+ <tr>
+ <td>
+ <para style="Standard">Name: [[ module.shortdesc]]</para>
+ </td>
+ <td>
+ <para style="Standard">Version: [[module.latest_version]]</para>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <para style="Standard">Directory: [[ module.name ]]</para>
+ </td>
+ <td>
+ <para style="Standard">Web: [[ module.website ]]</para>
+ </td>
+ </tr>
+ </blockTable>
+ <para style="P6">[[ module.description ]]</para>
+ <section>
+ <para style="Standard">[[ repeatIn(findobj(module.name) ,'object') ]]</para>
+ <blockTable colWidths="510.0" repeatRows="1" style="Tableau3">
+ <tr>
+ <td>
+ <para style="P7">Object: [[ object.name ]]</para>
+ <pre style="P8">[[ objdoc(object.model) ]]</pre>
+ </td>
+ </tr>
+ </blockTable>
+ <blockTable colWidths="113.0,397.0" repeatRows="1" style="Tableau4">
+ <tr>
+ <td>
+ <para style="P9"><font face="Times-Roman">[[ repeatIn(findflds(object.model), 'field') ]]</font> [[ field[0] ]]</para>
+ </td>
+ <td>
+ <para style="Standard">
+ <font face="Times-Roman">[[ field[1].get('string', 'Unknown') ]], [[ field[1]['type'] ]] [[field[1].get('required',False) and ', required']] [[field[1].get('readonly',False) and ', readonly']] </font>
+ </para>
+ <para style="Standard">[[ field[1].get('help', '') ]]</para>
+ </td>
+ </tr>
+ </blockTable>
+ <para style="Standard">
+ <font color="white"> </font>
+ </para>
+ </section>
+ <para style="Standard">
+ <font color="white"> </font>
+ </para>
+ <para style="Standard">
+ <font color="white"> </font>
+ </para>
+ <para style="Standard">
+ <font color="white"> </font>
+ </para>
+ </section>
+ <para style="Standard">
+ <font color="white"> </font>
+ </para>
+ </story>
+</document>
+
diff --git a/bin/addons/base/module/report/ir_module_reference.sxw b/bin/addons/base/module/report/ir_module_reference.sxw
new file mode 100644
index 00000000000..b97959a8d0a
--- /dev/null
+++ b/bin/addons/base/module/report/ir_module_reference.sxw
Binary files differ
diff --git a/bin/addons/base/module/report/ir_module_reference_print.py b/bin/addons/base/module/report/ir_module_reference_print.py
new file mode 100644
index 00000000000..3b3d3a7be7a
--- /dev/null
+++ b/bin/addons/base/module/report/ir_module_reference_print.py
@@ -0,0 +1,60 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import time
+from report import report_sxw
+
+class ir_module_reference_print(report_sxw.rml_parse):
+ def __init__(self, cr, uid, name, context):
+ super(ir_module_reference_print, self).__init__(cr, uid, name, context)
+ self.localcontext.update({
+ 'time': time,
+ 'findobj': self._object_find,
+ 'objdoc': self._object_doc,
+ 'findflds': self._fields_find,
+ })
+ def _object_doc(self, obj):
+ modobj = self.pool.get(obj)
+ return modobj.__doc__
+
+ def _object_find(self, module):
+ modobj = self.pool.get('ir.model')
+ if module=='base':
+ ids = modobj.search(self.cr, self.uid, [('model','=like','res%')])
+ ids += modobj.search(self.cr, self.uid, [('model','=like','ir%')])
+ else:
+ ids = modobj.search(self.cr, self.uid, [('model','=like',module+'%')])
+ return modobj.browse(self.cr, self.uid, ids)
+
+ def _fields_find(self, obj):
+ modobj = self.pool.get(obj)
+ res = modobj.fields_get(self.cr, self.uid).items()
+ return res
+
+report_sxw.report_sxw('report.ir.module.reference', 'ir.module.module', 'addons/base/module/report/ir_module_reference.rml', parser=ir_module_reference_print)
+
diff --git a/bin/addons/base/module/wizard/__init__.py b/bin/addons/base/module/wizard/__init__.py
new file mode 100644
index 00000000000..d74449872c0
--- /dev/null
+++ b/bin/addons/base/module/wizard/__init__.py
@@ -0,0 +1,35 @@
+##############################################################################
+#
+# Copyright (c) 2005-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard_install_module
+import wizard_update_module
+import wizard_module_upgrade
+import wizard_module_info_get
+import wizard_module_lang_install
+import add_new
+import wizard_export_lang
+import wizard_import_lang
diff --git a/bin/addons/base/module/wizard/add_new.py b/bin/addons/base/module/wizard/add_new.py
new file mode 100644
index 00000000000..63d7a2359dd
--- /dev/null
+++ b/bin/addons/base/module/wizard/add_new.py
@@ -0,0 +1,100 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be)
+#
+# $Id$
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contact a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import os
+import re
+import glob
+import time
+import imp
+
+import tools
+import wizard
+import pooler
+
+module_name_re = re.compile('.*addons.(.*?).__terp__.py$')
+
+_info_arch = '''<?xml version="1.0"?>
+<form string="Scan for new modules">
+ <label string="This function will check if you installed new modules in the 'addons' path of your server installation." colspan="4" />
+</form>
+'''
+_info_fields = {}
+
+def get_module_info(name):
+ try:
+ f = file(os.path.join(tools.config['addons_path'], name, '__terp__.py'), 'r')
+ data = f.read()
+ info = eval(data)
+ f.close()
+ except:
+ return {}
+ return info
+
+class wizard_install_module(wizard.interface):
+ def watch_dir(self, cr, uid, data, context):
+ mod_obj = pooler.get_pool(cr.dbname).get('ir.module.module')
+ all_mods = mod_obj.read(cr, uid, mod_obj.search(cr, uid, []), ['name', 'state'])
+ known_modules = set([x['name'] for x in all_mods])
+ ls_ad = glob.glob(os.path.join(tools.config['addons_path'], '*', '__terp__.py'))
+ modules = set([module_name_re.match(name).group(1) for name in ls_ad])
+ new_modules = modules - known_modules
+ for module in new_modules:
+ terp = get_module_info(module)
+ if not terp.get('installable', True):
+ continue
+ imp.load_module(module, *imp.find_module(module))
+ mod_id = mod_obj.create(cr, uid, {
+ 'name': module,
+ 'state': 'uninstalled',
+ 'description': terp.get('description', ''),
+ 'shortdesc': terp.get('name', ''),
+ 'author': terp.get('author', 'Unknown')})
+ dependencies = terp.get('depends', [])
+ for d in dependencies:
+ cr.execute('insert into ir_module_module_dependency (module_id,name) values (%s, %s)', (mod_id, d))
+ for module in known_modules:
+ terp = get_module_info(module)
+ if terp.get('installable', True):
+ for mod in all_mods:
+ if mod['name'] == module and mod['state'] == 'uninstallable':
+ mod_obj.write(cr, uid, [mod['id']], {'state': 'uninstalled'})
+ return {}
+
+ states = {
+ 'init': {
+ 'actions': [],
+ 'result': {'type':'form', 'arch': _info_arch, 'fields': _info_fields, 'state':[('end','Cancel','gtk-cancel'),('addmod','Check new modules','gtk-ok')]}
+ },
+ 'addmod': {
+ 'actions': [watch_dir],
+ 'result': {'type':'state', 'state':'end'}
+ },
+ }
+wizard_install_module('module.module.scan')
+
diff --git a/bin/addons/base/module/wizard/wizard_export_lang.py b/bin/addons/base/module/wizard/wizard_export_lang.py
new file mode 100644
index 00000000000..212e6b459c2
--- /dev/null
+++ b/bin/addons/base/module/wizard/wizard_export_lang.py
@@ -0,0 +1,91 @@
+# -*- coding: iso-8859-1 -*-
+##############################################################################
+#
+# Copyright (c) 2005-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import tools
+import base64
+import StringIO
+import csv
+import pooler
+
+view_form_init="""<?xml version="1.0"?>
+<form string="Export language">
+ <image name="gtk-info" size="64" colspan="2"/>
+ <group colspan="2" col="4">
+ <separator string="Export translation file" colspan="4"/>
+ <label align="0.0" string="Choose a language to export:" colspan="4"/>
+ <field name="lang" colspan="4"/>
+ </group>
+</form>"""
+
+view_form_finish="""<?xml version="1.0"?>
+<form string="Export language">
+ <image name="gtk-info" size="64" colspan="2"/>
+ <group colspan="2" col="4">
+ <separator string="Export done" colspan="4"/>
+ <field name="data" readonly="1" colspan="3"/>
+ <label align="0.0" string="Save this document to a .CSV file and open it with\n your favourite spreadsheet software. The file\n encoding is UTF-8. You have to translate the latest\n column before reimporting it." colspan="4"/>
+ </group>
+</form>"""
+
+class wizard_export_lang(wizard.interface):
+ def _get_language(self, cr, uid, context):
+ lang_obj=pooler.get_pool(cr.dbname).get('res.lang')
+ ids=lang_obj.search(cr, uid, [('active', '=', True),])
+ langs=lang_obj.browse(cr, uid, ids)
+ return [(lang.code, lang.translatable and lang.name or 'New language') for lang in langs]
+
+ def _get_file(self, cr, uid, data, context):
+ file=tools.trans_generate(data['form']['lang'], 'all')
+ buf=StringIO.StringIO()
+ writer=csv.writer(buf)
+ for row in file:
+ writer.writerow(row)
+ del file
+ out=base64.encodestring(buf.getvalue())
+ buf.close()
+ return {'data': out}
+
+ fields_form={
+ 'lang': {'string':'Language', 'type':'selection', 'selection':_get_language,},
+ }
+ fields_form_finish={
+ 'data': {'string':'File', 'type':'binary', 'readonly': True,},
+ }
+ states={
+ 'init':{
+ 'actions':[],
+ 'result':{'type':'form', 'arch':view_form_init, 'fields': fields_form, 'state':[('end', 'Cancel', 'gtk-cancel'),('finish', 'Ok', 'gtk-ok')]}
+ },
+ 'finish':{
+ 'actions':[_get_file],
+ 'result':{'type':'form', 'arch':view_form_finish, 'fields': fields_form_finish, 'state':[('end', 'Close', 'gtk-cancel')]}
+ },
+ }
+wizard_export_lang('module.lang.export')
diff --git a/bin/addons/base/module/wizard/wizard_import_lang.py b/bin/addons/base/module/wizard/wizard_import_lang.py
new file mode 100644
index 00000000000..226bece1ace
--- /dev/null
+++ b/bin/addons/base/module/wizard/wizard_import_lang.py
@@ -0,0 +1,75 @@
+# -*- coding: iso-8859-1 -*-
+##############################################################################
+#
+# Copyright (c) 2005-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import tools
+import base64
+import pooler
+
+view_form="""<?xml version="1.0"?>
+<form string="Import language">
+ <image name="gtk-info" size="64" colspan="2"/>
+ <group colspan="2" col="4">
+ <separator string="Import new language" colspan="4"/>
+ <field name="name"/>
+ <field name="code"/>
+ <field name="data" colspan="3"/>
+ <label string="You have to import a .CSV file wich is encoded in UTF-8.\nPlease check that the first line of your file is:" colspan="4" align="0.0"/>
+ <label string="type,name,res_id,src,value" colspan="4"/>
+ </group>
+</form>"""
+
+class wizard_import_lang(wizard.interface):
+ def _import_lang(self, cr, uid, data, context):
+ form=data['form']
+ lang_obj=pooler.get_pool(cr.dbname).get('res.lang')
+ ids=lang_obj.search(cr, uid, [('code', '=', form['code'])])
+ if ids:
+ lang_obj.write(cr, uid, ids, {'name': form['name']})
+ else:
+ lang_obj.create(cr, uid, {'code': form['code'], 'name': form['name']})
+ buf=base64.decodestring(data['form']['data']).split('\n')
+ tools.trans_load_data(cr.dbname, buf, data['form']['code'])
+ return {}
+ fields_form={
+ 'name':{'string':'Language name', 'type':'char', 'size':64, 'required':True},
+ 'code':{'string':'Code (eg:en_US)', 'type':'char', 'size':5, 'required':True},
+ 'data':{'string':'File', 'type':'binary', 'required':True},
+ }
+ states={
+ 'init':{
+ 'actions':[],
+ 'result':{'type':'form', 'arch':view_form, 'fields':fields_form, 'state':[('end', 'Cancel', 'gtk-cancel'), ('finish', 'Ok', 'gtk-ok')]}
+ },
+ 'finish':{
+ 'actions':[],
+ 'result':{'type':'action', 'action':_import_lang, 'state':'end'}
+ },
+ }
+wizard_import_lang('module.lang.import')
diff --git a/bin/addons/base/module/wizard/wizard_install_module.py b/bin/addons/base/module/wizard/wizard_install_module.py
new file mode 100644
index 00000000000..71a6be50848
--- /dev/null
+++ b/bin/addons/base/module/wizard/wizard_install_module.py
@@ -0,0 +1,67 @@
+# -*- coding: iso-8859-1 -*-
+##############################################################################
+#
+# Copyright (c) 2005-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import netsvc
+
+#TODO: upgraded modules
+#TODO: removed modules
+additional_changes_form = '''<?xml version="1.0"?>
+<form string="Additional modules">
+ <label string="The following additional modules need to be installed:"/>
+ <newline/>
+ <field name="additional"/>
+</form>'''
+
+additional_changes_fields = {
+ 'additional': {'string':'Additional modules', 'type':'one2many', 'relation':'ir.module.module', 'readonly':True},
+}
+
+class wizard_install_module(wizard.interface):
+ def _get_value(self, cr, uid, data, context):
+ service = netsvc.LocalService("object_proxy")
+ extra = service.execute(cr.dbname, uid, 'ir.module.module', 'get_extra_modules', data['ids'])
+ return {'additional': extra}
+
+ def _install_module(self, cr, uid, data, context):
+ service = netsvc.LocalService("object_proxy")
+ res = service.execute(cr.dbname, uid, 'ir.module.module', 'install', data['ids'])
+ return {}
+
+ states = {
+ 'init': {
+ 'actions': [_get_value],
+ 'result': {'type':'form', 'arch':additional_changes_form, 'fields':additional_changes_fields, 'state':[('install','Install'), ('end','Cancel')]}
+ },
+ 'install': {
+ 'actions': [_install_module],
+ 'result': {'type':'state', 'state':'end'}
+ },
+ }
+wizard_install_module('module.module.install')
+
diff --git a/bin/addons/base/module/wizard/wizard_module_info_get.py b/bin/addons/base/module/wizard/wizard_module_info_get.py
new file mode 100644
index 00000000000..124a6820b8b
--- /dev/null
+++ b/bin/addons/base/module/wizard/wizard_module_info_get.py
@@ -0,0 +1,46 @@
+# -*- coding: iso-8859-1 -*-
+##############################################################################
+#
+# Copyright (c) 2005-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import netsvc
+
+class wizard_info_get(wizard.interface):
+ def _update_module(self, cr, uid, data, context):
+ service = netsvc.LocalService("object_proxy")
+ service.execute(cr.dbname, uid, 'ir.module.module', 'info_get', data['ids'])
+ return {}
+
+ states = {
+ 'init': {
+ 'actions': [_update_module],
+ 'result': {'type':'state', 'state':'end'}
+ },
+ }
+wizard_info_get('module.info_get')
+
diff --git a/bin/addons/base/module/wizard/wizard_module_lang_install.py b/bin/addons/base/module/wizard/wizard_module_lang_install.py
new file mode 100644
index 00000000000..14f3e867a50
--- /dev/null
+++ b/bin/addons/base/module/wizard/wizard_module_lang_install.py
@@ -0,0 +1,83 @@
+# -*- coding: iso-8859-1 -*-
+##############################################################################
+#
+# Copyright (c) 2005-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import tools
+
+view_form_end = """<?xml version="1.0"?>
+<form string="Language file loaded.">
+ <image name="gtk-info" size="64" colspan="2"/>
+ <group colspan="2" col="4">
+ <separator string="Installation done" colspan="4"/>
+ <label align="0.0" string="The selected language has been successfully installed.\nYou must change the preferences of the user and open a new menu to view changes." colspan="4"/>
+ </group>
+</form>"""
+
+view_form = """<?xml version="1.0"?>
+<form string="System Upgrade">
+ <image name="gtk-info" size="64" colspan="2"/>
+ <group colspan="2" col="4">
+ <separator string="System Upgrade" colspan="4"/>
+ <label align="0.0" string="Choose a language to install:" colspan="4"/>
+ <field name="lang" colspan="4"/>
+ <label align="0.0" string="Note that this operation may take a few minutes." colspan="4"/>
+ </group>
+</form>"""
+
+
+class wizard_lang_install(wizard.interface):
+ def _lang_install(self, cr, uid, data, context):
+ print cr, uid, data, context
+
+ lang = data['form']['lang']
+ if lang and lang != 'en_EN':
+ filename = tools.config["root_path"] + "/i18n/" + lang + ".csv"
+ tools.trans_load(cr.dbname, filename, lang)
+ return {}
+
+ def _get_language(sel, cr, uid, context):
+ return tools.scan_languages()
+
+ fields_form = {
+ 'lang': {'string':'Language', 'type':'selection', 'selection':_get_language,
+ },
+ }
+
+ states = {
+ 'init': {
+ 'actions': [],
+ 'result': {'type':'form', 'arch':view_form, 'fields': fields_form, 'state':[('end','Cancel','gtk-cancel'),('start','Start installation','gtk-ok')]}
+ },
+ 'start': {
+ 'actions': [_lang_install],
+ 'result': {'type':'form', 'arch':view_form_end, 'fields': {}, 'state':[('end','Ok','gtk-ok')]}
+ },
+ }
+wizard_lang_install('module.lang.install')
+
diff --git a/bin/addons/base/module/wizard/wizard_module_upgrade.py b/bin/addons/base/module/wizard/wizard_module_upgrade.py
new file mode 100644
index 00000000000..65cfaa66a50
--- /dev/null
+++ b/bin/addons/base/module/wizard/wizard_module_upgrade.py
@@ -0,0 +1,81 @@
+# -*- coding: iso-8859-1 -*-
+##############################################################################
+#
+# Copyright (c) 2005-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import pooler
+
+view_form_end = """<?xml version="1.0"?>
+<form string="System upgrade done">
+ <separator string="System upgrade done"/>
+ <label align="0.0" string="The modules have been upgraded / installed !" colspan="4"/>
+ <label align="0.0" string="You may have to reinstall some language pack." colspan="4"/>
+ <label align="0.0" string="We suggest you to close all tabs and open a new menu window (Ctrl T)." colspan="4"/>
+</form>"""
+
+view_form = """<?xml version="1.0"?>
+<form string="System Upgrade">
+ <image name="gtk-info" size="64" colspan="2"/>
+ <group colspan="2" col="4">
+ <label align="0.0" string="Your system will be upgraded." colspan="4"/>
+ <label align="0.0" string="Note that this operation my take a few minutes." colspan="4"/>
+ <separator string="Modules to update"/>
+ <field name="module_info" nolabel="1" colspan="4"/>
+ </group>
+</form>"""
+
+view_field = {
+ "module_info": {'type':'text', 'string':'Modules', 'readonly':True}
+}
+
+class wizard_info_get(wizard.interface):
+ def _get_install(self, cr, uid, data, context):
+ pool=pooler.get_pool(cr.dbname)
+ ids = pool.get('ir.module.module').search(cr, uid, [
+ ('state','<>','uninstalled'),
+ ('state','<>','installed'),
+ ('state','<>','uninstallable')])
+ res = pool.get('ir.module.module').read(cr, uid, ids, ['name','state'], context)
+ return {'module_info':'\n'.join(map(lambda x: x['name']+' : '+x['state'], res))}
+
+ def _upgrade_module(self, cr, uid, data, context):
+ (db, pool)=pooler.restart_pool(cr.dbname, update_module=True)
+ return {}
+
+ states = {
+ 'init': {
+ 'actions': [_get_install],
+ 'result': {'type':'form', 'arch':view_form, 'fields': view_field, 'state':[('end','Cancel','gtk-cancel'),('start','Start Upgrade','gtk-ok')]}
+ },
+ 'start': {
+ 'actions': [_upgrade_module],
+ 'result': {'type':'form', 'arch':view_form_end, 'fields': {}, 'state':[('end','Close','gtk-close')]}
+ },
+ }
+wizard_info_get('module.upgrade')
+
diff --git a/bin/addons/base/module/wizard/wizard_update_module.py b/bin/addons/base/module/wizard/wizard_update_module.py
new file mode 100644
index 00000000000..5a2ec58912b
--- /dev/null
+++ b/bin/addons/base/module/wizard/wizard_update_module.py
@@ -0,0 +1,61 @@
+# -*- coding: iso-8859-1 -*-
+##############################################################################
+#
+# Copyright (c) 2005-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import netsvc
+
+class wizard_update_module(wizard.interface):
+ def _update_module(self, cr, uid, data, context):
+ service = netsvc.LocalService("object_proxy")
+ service.execute(cr.dbname, uid, 'ir.module.module', 'update_list')
+ return {}
+
+ def _action_module_open(self, cr, uid, data, context):
+ return {
+ 'domain': "[]",
+ 'name': 'Open Module List',
+ 'view_type': 'form',
+ 'view_mode': 'tree,form',
+ 'res_model': 'ir.module.module',
+ 'view_id': False,
+ 'type': 'ir.actions.act_window'
+ }
+
+ states = {
+ 'init': {
+ 'actions': [_update_module],
+ 'result': {'type':'state', 'state':'open_window'}
+ },
+ 'open_window': {
+ 'actions': [],
+ 'result': {'type': 'action', 'action': _action_module_open, 'state':'end'}
+ }
+ }
+wizard_update_module('module.module.update')
+
diff --git a/bin/addons/base/report/custom.xsl b/bin/addons/base/report/custom.xsl
new file mode 100644
index 00000000000..e40c62ddb2a
--- /dev/null
+++ b/bin/addons/base/report/custom.xsl
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
+
+<xsl:template match="/">
+ <xsl:apply-templates select="report"/>
+</xsl:template>
+
+<xsl:template match="report">
+ <document filename="example_5.pdf">
+ <template leftMargin="1.5cm" rightMargin="1.5cm" topMargin="1.5cm" bottomMargin="1.5cm" title="Reporting" author="Generated by Tiny ERP, Fabien Pinckaers">
+ <xsl:attribute name="pageSize">
+ <xsl:value-of select="/report/config/pageSize"/>
+ </xsl:attribute>
+ <pageTemplate id="first">
+ <pageGraphics>
+ <drawCentredString>
+ <xsl:attribute name="x">
+ <xsl:value-of select="/report/config/pageWidth div 2"/>
+ </xsl:attribute>
+ <xsl:attribute name="y">
+ <xsl:value-of select="/report/config/pageHeight - 56.69"/>
+ </xsl:attribute>
+ <xsl:value-of select="config/report-header"/>
+ </drawCentredString>
+
+ <fill color="(0.2,0.2,0.2)"/>
+ <setFont name="Helvetica" size="10"/>
+ <drawCentredString y="10mm">
+ <xsl:attribute name="x">
+ <xsl:value-of select="/report/config/pageWidth div 2"/>
+ </xsl:attribute>
+ <xsl:value-of select="config/report-footer"/>
+ </drawCentredString>
+
+ <fill color="(0.969,0.2,0.2)"/>
+ <setFont name="Helvetica" size="8"/>
+ <drawString x="1.5cm">
+ <xsl:attribute name="y">
+ <xsl:value-of select="/report/config/pageHeight - 56.69"/>
+ </xsl:attribute><xsl:text>Tiny ERP</xsl:text>
+ </drawString>
+
+ <fill color="(0.2,0.2,0.2)"/>
+ <drawRightString>
+ <xsl:attribute name="x">
+ <xsl:value-of select="/report/config/pageWidth - 45"/>
+ </xsl:attribute>
+ <xsl:attribute name="y">
+ <xsl:value-of select="/report/config/pageHeight - 56.69"/>
+ </xsl:attribute><xsl:value-of select="/report/config/date"/>
+ </drawRightString>
+ </pageGraphics>
+ <frame id="column" x1="1.5cm" y1="1.5cm">
+ <xsl:attribute name="width">
+ <xsl:value-of select="/report/config/pageWidth - 85"/>
+ </xsl:attribute>
+ <xsl:attribute name="height">
+ <xsl:value-of select="/report/config/pageHeight - 100"/>
+ </xsl:attribute>
+ </frame>
+ </pageTemplate>
+ </template>
+ <stylesheet>
+ <paraStyle name="sum" fontName="Helvetica" textColor="green"/>
+ <paraStyle name="sum_float" fontName="Helvetica" alignment="right" textColor="green"/>
+ <paraStyle name="sum_end" fontName="Helvetica" textColor="red"/>
+ <paraStyle name="sum_float_end" fontName="Helvetica" alignment="right" textColor="red"/>
+ <blockTableStyle id="table">
+ <blockValign value="TOP"/>
+ <blockFont name="Helvetica" size="12" start="0,0" stop="-1,0"/>
+ <blockBackground colorName="(0.8,0.8,0.8)" start="0,0" stop="-1,0"/>
+
+ <lineStyle kind="GRID" colorName="lightgrey" thickness="0.3"/>
+
+ <lineStyle kind="LINEBELOW" colorName="black" thickness="1.5" start="0,0" stop="-1,0"/>
+ <lineStyle kind="LINEABOVE" colorName="black" thickness="1.5" start="0,0" stop="-1,0"/>
+ </blockTableStyle>
+ </stylesheet>
+ <story>
+ <blockTable style="table">
+ <xsl:attribute name="colWidths">
+ <xsl:value-of select="/report/config/tableSize"/>
+ </xsl:attribute>
+ <xsl:apply-templates select="header"/>
+ <xsl:apply-templates select="group"/>
+
+ <xsl:if test="group//@sum">
+ <tr>
+ <xsl:for-each select="group[position()=1]/record[position()=1]/field">
+ <xsl:choose>
+ <xsl:when test="position()=1">
+ <td><para style="sum_end">SUM</para></td>
+ </xsl:when>
+ <xsl:when test="@sum">
+ <xsl:variable name="ici">
+ <xsl:value-of select="@id"/>
+ </xsl:variable>
+ <td><para style="sum_float_end"><xsl:value-of select="sum(../../../group/record/field[@id=$ici])"/></para></td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </tr>
+ </xsl:if>
+ <xsl:if test="group//@count">
+ <tr>
+ <xsl:for-each select="group[position()=1]/record[position()=1]/field">
+ <xsl:choose>
+ <xsl:when test="position()=1">
+ <td><para style="sum_end">#</para></td>
+ </xsl:when>
+ <xsl:when test="@count">
+ <xsl:variable name="ici">
+ <xsl:value-of select="@id"/>
+ </xsl:variable>
+ <td><para style="sum_float_end"><xsl:value-of select="count(../../../group/record/field[@id=$ici])"/></para></td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </tr>
+ </xsl:if>
+ <xsl:if test="group//@avg">
+ <tr>
+ <xsl:for-each select="group[position()=1]/record[position()=1]/field">
+ <xsl:choose>
+ <xsl:when test="position()=1">
+ <td><para style="sum_end">AVG</para></td>
+ </xsl:when>
+ <xsl:when test="@avg">
+ <xsl:variable name="ici">
+ <xsl:value-of select="@id"/>
+ </xsl:variable>
+ <td><para style="sum_float_end"><xsl:value-of select="sum(../../../group/record/field[@id=$ici]) div count(../../../group/record/field[@id=$ici])"/></para></td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </tr>
+ </xsl:if>
+ </blockTable>
+ </story>
+ </document>
+</xsl:template>
+
+<xsl:template match="header">
+ <tr>
+ <xsl:apply-templates select="field"/>
+ </tr>
+</xsl:template>
+
+<xsl:template match="group">
+ <xsl:if test="not(/report/config/totalonly)">
+ <xsl:apply-templates select="record"/>
+ </xsl:if>
+ <xsl:if test="/report/config/groupby">
+ <!-- SUM -->
+ <xsl:if test="//@sum">
+ <tr>
+ <xsl:for-each select="record[position()=1]/field">
+ <xsl:choose>
+ <xsl:when test="/report/config/groupby = @id">
+ <td><para style="sum"><xsl:value-of select="."/> (SUM)</para></td>
+ </xsl:when>
+ <xsl:when test="@sum">
+ <xsl:variable name="ici">
+ <xsl:value-of select="@id"/>
+ </xsl:variable>
+ <td><para style="sum_float"><xsl:value-of select="sum(../../record/field[@id=$ici])"/></para></td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </tr>
+ </xsl:if>
+ <!-- COUNT -->
+ <xsl:if test="//@count">
+ <tr>
+ <xsl:for-each select="record[position()=1]/field">
+ <xsl:choose>
+ <xsl:when test="/report/config/groupby = @id">
+ <td><para style="sum"><xsl:value-of select="."/> (#)</para></td>
+ </xsl:when>
+ <xsl:when test="@count">
+ <xsl:variable name="ici">
+ <xsl:value-of select="@id"/>
+ </xsl:variable>
+ <td><para style="sum_float"><xsl:value-of select="count(../../record/field[@id=$ici])"/></para></td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </tr>
+ </xsl:if>
+ <!-- AVG -->
+ <xsl:if test="//@avg">
+ <tr>
+ <xsl:for-each select="record[position()=1]/field">
+ <xsl:choose>
+ <xsl:when test="/report/config/groupby = @id">
+ <td><para style="sum"><xsl:value-of select="."/> (AVG)</para></td>
+ </xsl:when>
+ <xsl:when test="@avg">
+ <xsl:variable name="ici">
+ <xsl:value-of select="@id"/>
+ </xsl:variable>
+ <td><para style="sum_float"><xsl:value-of select="sum(../../record/field[@id=$ici]) div count(../../record/field[@id=$ici])"/></para></td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </tr>
+ </xsl:if>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="record">
+ <tr>
+ <xsl:apply-templates select="field"/>
+ </tr>
+</xsl:template>
+
+<xsl:template match="field">
+ <xsl:choose>
+ <xsl:when test="@level">
+ <td><pre><xsl:value-of select="@level"/><xsl:value-of select="."/></pre></td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td><para><xsl:value-of select="."/></para></td>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/bin/addons/base/report/custom_default.xsl b/bin/addons/base/report/custom_default.xsl
new file mode 100644
index 00000000000..c9194789f93
--- /dev/null
+++ b/bin/addons/base/report/custom_default.xsl
@@ -0,0 +1,36 @@
+<?xml version = '1.0' encoding="utf-8"?>
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
+ <xsl:variable name="signature" select="//corporate-header/signature"/>
+ <xsl:variable name="title">Tiny ERP Report</xsl:variable>
+ <xsl:variable name="leftMargin">1cm</xsl:variable>
+ <xsl:variable name="rightMargin">1cm</xsl:variable>
+ <xsl:variable name="topMargin">1cm</xsl:variable>
+ <xsl:variable name="bottomMargin">1cm</xsl:variable>
+ <xsl:variable name="pageSize">21cm,29.7cm</xsl:variable>
+
+ <xsl:variable name="page_format">a4_letter</xsl:variable>
+
+ <xsl:template name="first_page_frames">
+ <frame id="column" x1="1.5cm" y1="1.5cm">
+ <xsl:attribute name="width">
+ <xsl:value-of select="/report/config/PageWidth - 85"/>
+ </xsl:attribute>
+ <xsl:attribute name="height">
+ <xsl:value-of select="/report/config/PageHeight - 100"/>
+ </xsl:attribute>
+ </frame>
+ </xsl:template>
+
+ <xsl:template name="other_pages_frames">
+ <frame id="column" x1="1.5cm" y1="1.5cm">
+ <xsl:attribute name="width">
+ <xsl:value-of select="/report/config/PageWidth - 85"/>
+ </xsl:attribute>
+ <xsl:attribute name="height">
+ <xsl:value-of select="/report/config/PageHeight - 100"/>
+ </xsl:attribute>
+ </frame>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/bin/addons/base/report/custom_new.xsl b/bin/addons/base/report/custom_new.xsl
new file mode 100644
index 00000000000..2788c4d2ed9
--- /dev/null
+++ b/bin/addons/base/report/custom_new.xsl
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
+
+ <xsl:import href="../../base/report/custom_default.xsl"/>
+ <xsl:import href="../../base/report/custom_rml.xsl"/>
+ <xsl:template match="/">
+ <xsl:call-template name="rml">
+ <xsl:with-param name="pageSize" select="report/config/PageSize"/>
+ <xsl:with-param name="page_format" select="report/config/PageFormat"/>
+ </xsl:call-template>
+ </xsl:template>
+
+ <!-- stylesheet -->
+
+ <xsl:template name="stylesheet">
+ <paraStyle name="title" fontName="Helvetica-Bold" fontSize="22" alignment="center"/>
+
+ <blockTableStyle id="products">
+ <blockBackground colorName="grey" start="0,0" stop="-1,0"/>
+ <blockValign value="TOP"/>
+ <blockAlignment value="RIGHT"/>
+ <lineStyle kind="GRID" colorName="black"/>
+ </blockTableStyle>
+ </xsl:template>
+
+ <xsl:template name="story">
+ <xsl:apply-templates select="report"/>
+ </xsl:template>
+
+ <xsl:template match="report">
+ <xsl:apply-templates select="config"/>
+ <!--<setNextTemplate name="other_pages"/>-->
+ <blockTable style="products">
+ <xsl:if test="string-length(./config/tableSize)&gt;1">
+ <xsl:attribute name="colWidths">
+ <xsl:value-of select="./config/tableSize"/>
+ </xsl:attribute>
+ </xsl:if>
+
+ <xsl:apply-templates select="header"/>
+ <xsl:apply-templates select="lines"/>
+ </blockTable>
+ </xsl:template>
+
+ <xsl:template match="config">
+ <para style="title">
+ <xsl:value-of select="report-header"/>
+ </para>
+ <spacer length="1cm" width="2mm"/>
+ </xsl:template>
+
+ <xsl:template match="header">
+ <tr>
+ <xsl:for-each select="field">
+ <td>
+ <para>
+ <xsl:value-of select="."/>
+ </para>
+ </td>
+ </xsl:for-each>
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="lines">
+ <xsl:apply-templates select="row"/>
+ </xsl:template>
+
+ <xsl:template match="row">
+ <tr>
+ <xsl:apply-templates select="col"/>
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="col">
+ <td>
+ <xsl:choose>
+ <xsl:when test="@para='yes'">
+ <xsl:choose>
+ <xsl:when test="@tree='yes'">
+ <para>
+ <xsl:attribute name="leftIndent"><xsl:value-of select="@space"/></xsl:attribute>
+ <xsl:value-of select="."/>
+ </para>
+ </xsl:when>
+ <xsl:otherwise>
+ <para>
+ <xsl:value-of select="."/>
+ </para>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xpre>
+ <xsl:value-of select="."/>
+ </xpre>
+ </xsl:otherwise>
+ </xsl:choose>
+ </td>
+ </xsl:template>
+
+
+<!-- <xsl:template match="col">
+ <td>
+ <xsl:if test="@tree='yes'">
+ <xsl:choose>
+ <xsl:when test="@para='yes'">
+ <para>
+ <xsl:attribute name="leftIndent"><xsl:value-of select="@space"/></xsl:attribute>
+ <xsl:value-of select="."/>
+ </para>
+ </xsl:when>
+ <xsl:otherwise>
+ <xpre>
+ <xsl:value-of select="."/>
+ </xpre>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:if>
+ <xsl:if test="@tree!='yes'">
+ <xpre>
+ <xsl:value-of select="."/>
+ </xpre>
+ </xsl:if>
+ </td>
+ </xsl:template>
+-->
+</xsl:stylesheet>
diff --git a/bin/addons/base/report/custom_rml.xsl b/bin/addons/base/report/custom_rml.xsl
new file mode 100644
index 00000000000..afc8c3d9a92
--- /dev/null
+++ b/bin/addons/base/report/custom_rml.xsl
@@ -0,0 +1,62 @@
+<?xml version = '1.0' encoding="utf-8"?>
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
+
+ <xsl:template name="first_page_graphics_report"/>
+ <xsl:template name="other_pages_graphics_report"/>
+
+ <xsl:template name="rml">
+ <document>
+ <template author="Generated by Tiny ERP, Fabien Pinckaers">
+ <xsl:attribute name="pageSize">
+ <xsl:value-of select="$pageSize"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="leftMargin">
+ <xsl:value-of select="$leftMargin"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="rightMargin">
+ <xsl:value-of select="$rightMargin"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="topMargin">
+ <xsl:value-of select="$topMargin"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="bottomMargin">
+ <xsl:value-of select="$bottomMargin"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="title">
+ <xsl:value-of select="$title"/>
+ </xsl:attribute>
+
+ <pageTemplate id="first_page">
+ <pageGraphics>
+ <xsl:call-template name="first_page_graphics_report"/>
+ </pageGraphics>
+
+ <xsl:call-template name="first_page_frames"/>
+ </pageTemplate>
+
+ <pageTemplate id="other_pages">
+ <pageGraphics>
+ <xsl:call-template name="other_pages_graphics_report"/>
+ </pageGraphics>
+
+ <xsl:call-template name="other_pages_frames"/>
+ </pageTemplate>
+ </template>
+
+ <stylesheet>
+ <xsl:call-template name="stylesheet"/>
+ </stylesheet>
+
+ <story>
+ <xsl:call-template name="story"/>
+ </story>
+
+ </document>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/bin/addons/base/report/rml_template.xsl b/bin/addons/base/report/rml_template.xsl
new file mode 100644
index 00000000000..8c956891133
--- /dev/null
+++ b/bin/addons/base/report/rml_template.xsl
@@ -0,0 +1,70 @@
+<?xml version = '1.0' encoding="utf-8"?>
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
+
+ <xsl:template name="first_page_graphics_report"/>
+ <xsl:template name="other_pages_graphics_report"/>
+<!--
+ <xsl:template name="first_page_graphics_corporation"/>
+ <xsl:template name="other_pages_graphics_corporation"/>
+
+ <xsl:template name="first_page_frames"/>
+ <xsl:template name="other_pages_frames"/>
+-->
+ <xsl:template name="rml">
+ <document>
+ <template author="Generated by Tiny ERP, Fabien Pinckaers">
+ <xsl:attribute name="pageSize">
+ <xsl:value-of select="$pageSize"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="leftMargin">
+ <xsl:value-of select="$leftMargin"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="rightMargin">
+ <xsl:value-of select="$rightMargin"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="topMargin">
+ <xsl:value-of select="$topMargin"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="bottomMargin">
+ <xsl:value-of select="$bottomMargin"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="title">
+ <xsl:value-of select="$title"/>
+ </xsl:attribute>
+
+ <pageTemplate id="first_page">
+ <pageGraphics>
+ <xsl:call-template name="first_page_graphics_corporation"/>
+ <xsl:call-template name="first_page_graphics_report"/>
+ </pageGraphics>
+
+ <xsl:call-template name="first_page_frames"/>
+ </pageTemplate>
+
+ <pageTemplate id="other_pages">
+ <pageGraphics>
+ <xsl:call-template name="other_pages_graphics_corporation"/>
+ <xsl:call-template name="other_pages_graphics_report"/>
+ </pageGraphics>
+
+ <xsl:call-template name="other_pages_frames"/>
+ </pageTemplate>
+ </template>
+
+ <stylesheet>
+ <xsl:call-template name="stylesheet"/>
+ </stylesheet>
+
+ <story>
+ <xsl:call-template name="story"/>
+ </story>
+
+ </document>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/bin/addons/base/res/__init__.py b/bin/addons/base/res/__init__.py
new file mode 100644
index 00000000000..b11b846ccef
--- /dev/null
+++ b/bin/addons/base/res/__init__.py
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import tools
+
+import partner
+import res_currency
+import res_company
+import res_user
+import res_request
+
+
+import ir_property
diff --git a/bin/addons/base/res/ir_property.py b/bin/addons/base/res/ir_property.py
new file mode 100644
index 00000000000..c37a7a21819
--- /dev/null
+++ b/bin/addons/base/res/ir_property.py
@@ -0,0 +1,77 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import osv,fields
+
+# -------------------------------------------------------------------------
+# Properties
+# -------------------------------------------------------------------------
+
+def _models_get2(self, cr, uid, context={}):
+ obj = self.pool.get('ir.model.fields')
+ ids = obj.search(cr, uid, [('view_load','=',1)])
+ res = []
+ done = {}
+ for o in obj.browse(cr, uid, ids, context=context):
+ if o.relation not in done:
+ res.append( [o.relation, o.relation])
+ done[o.relation] = True
+ return res
+
+def _models_get(self, cr, uid, context={}):
+ obj = self.pool.get('ir.model.fields')
+ ids = obj.search(cr, uid, [('view_load','=',1)])
+ res = []
+ done = {}
+ for o in obj.browse(cr, uid, ids, context=context):
+ if o.model_id.id not in done:
+ res.append( [o.model_id.model, o.model_id.name])
+ done[o.model_id.id] = True
+ return res
+
+class ir_property(osv.osv):
+ _name = 'ir.property'
+ _columns = {
+ 'name': fields.char('Name', size=128),
+ 'value': fields.reference('Value', selection=_models_get2, size=128),
+ 'res_id': fields.reference('Resource', selection=_models_get, size=128),
+ 'company_id': fields.many2one('res.company', 'Company'),
+ 'fields_id': fields.many2one('ir.model.fields', 'Fields', ondelete='cascade', required=True)
+ }
+ def get(self, cr, uid, name, model, res_id=False, context={}):
+ cr.execute('select id from ir_model_fields where name=%s and model=%s', (name, model))
+ res = cr.fetchone()
+ if res:
+ nid = self.search(cr, uid, [('fields_id','=',res[0]),('res_id','=',res_id)])
+ if nid:
+ d = self.browse(cr, uid, nid[0], context).value
+ return (d and int(d.split(',')[1])) or False
+ return False
+ir_property()
+
+
diff --git a/bin/addons/base/res/ir_property_view.xml b/bin/addons/base/res/ir_property_view.xml
new file mode 100644
index 00000000000..e41f388279a
--- /dev/null
+++ b/bin/addons/base/res/ir_property_view.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<terp>
+<data>
+ ==========================================================
+ Properties
+ ==========================================================
+
+ <record model="ir.ui.view" id="ir_property_view">
+ <field name="name">ir.property.form</field>
+ <field name="model">ir.property</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Properties">
+ <separator string="Property" colspan="4"/>
+ <field name="name" select="1"/>
+ <field name="company_id" select="1"/>
+ <field name="fields_id" colspan="3" select="1"/>
+ <field name="value" colspan="3"/>
+ <field name="res_id" colspan="3"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="ir_property_form">
+ <field name="name">ir.property</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.property</field>
+ <field name="view_type">form</field>
+ <field name="domain">[('res_id','=',False)]</field>
+ <field name="view_id" ref="ir_property_view"/>
+ </record>
+ <menuitem name="Administration/Custom/Properties/Default Properties" action="ir_property_form" id="menu_ir_property_form"/>
+
+ <record model="ir.actions.act_window" id="ir_property_form_all">
+ <field name="name">ir.property</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">ir.property</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="ir_property_view"/>
+ </record>
+ <menuitem name="Administration/Custom/Properties/All Properties" action="ir_property_form_all" id="menu_ir_property_form_all"/>
+
+ </data>
+</terp>
diff --git a/bin/addons/base/res/partner/__init__.py b/bin/addons/base/res/partner/__init__.py
new file mode 100644
index 00000000000..d3ce39fc290
--- /dev/null
+++ b/bin/addons/base/res/partner/__init__.py
@@ -0,0 +1,37 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import tools
+
+from partner import *
+from crm import *
+
+import wizard
+import report
+
+
diff --git a/bin/addons/base/res/partner/crm.py b/bin/addons/base/res/partner/crm.py
new file mode 100644
index 00000000000..ea60d5afbf4
--- /dev/null
+++ b/bin/addons/base/res/partner/crm.py
@@ -0,0 +1,105 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import time
+from osv import fields,osv
+
+
+#
+# Sale/Purchase Canal, Media
+#
+class res_partner_canal(osv.osv):
+ _name = "res.partner.canal"
+ _description = "Channels"
+ _columns = {
+ 'name': fields.char('Channel Name',size=64, required=True),
+ 'active': fields.boolean('Active'),
+ }
+ _defaults = {
+ 'active': lambda *a: 1,
+ }
+res_partner_canal()
+
+#
+# Partner: State of Mind
+#
+class res_partner_som(osv.osv):
+ _name = "res.partner.som"
+ _columns = {
+ 'name': fields.char('State of Mind',size=64, required=True),
+ 'factor': fields.float('Factor', required=True)
+ }
+res_partner_som()
+
+def _links_get(self, cr, uid, context={}):
+ obj = self.pool.get('res.request.link')
+ ids = obj.search(cr, uid, [])
+ res = obj.read(cr, uid, ids, ['object', 'name'], context)
+ return [(r['object'], r['name']) for r in res]
+
+class res_partner_event(osv.osv):
+ _name = "res.partner.event"
+ _columns = {
+ 'name': fields.char('Events',size=64, required=True),
+ 'som': fields.many2one('res.partner.som', 'State of Mind'),
+ 'description': fields.text('Description'),
+ 'planned_cost': fields.float('Planned Cost'),
+ 'planned_revenue': fields.float('Planned Revenue'),
+ 'probability': fields.float('Probability (0.50)'),
+ 'document': fields.reference('Document', selection=_links_get, size=128),
+ 'partner_id': fields.many2one('res.partner', 'Partner', select=True),
+ 'date': fields.datetime('Date', size=16),
+ 'user_id': fields.many2one('res.users', 'User'),
+ 'canal_id': fields.many2one('res.partner.canal', 'Channel'),
+ 'partner_type': fields.selection([('customer','Customer'),('retailer','Retailer'),('prospect','Commercial Prospect')], 'Partner Relation'),
+ 'type': fields.selection([('sale','Sale Opportunity'),('purchase','Purchase Offer'),('prospect','Prospect Contact')], 'Type of Event'),
+ 'event_ical_id': fields.char('iCal id', size=64),
+ }
+ _order = 'date desc'
+ _defaults = {
+ 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+ }
+res_partner_event()
+
+
+class res_partner_event_type(osv.osv):
+ _name = "res.partner.event.type"
+ _description = "Partner Events"
+ _columns = {
+ 'name': fields.char('Event Type',size=64, required=True),
+ 'key': fields.char('Key', size=64, required=True),
+ 'active': fields.boolean('Active'),
+ }
+ _defaults = {
+ 'active': lambda *a: 1
+ }
+ def check(self, cr, uid, key, context={}):
+ return self.search(cr, uid, [('key','=',key)])
+res_partner_event_type()
+
+
diff --git a/bin/addons/base/res/partner/crm_demo.xml b/bin/addons/base/res/partner/crm_demo.xml
new file mode 100644
index 00000000000..cbf82307bbd
--- /dev/null
+++ b/bin/addons/base/res/partner/crm_demo.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<terp>
+<data noupdate="1">
+ <record model="res.partner.canal">
+ <field name="name">website</field>
+ </record>
+ <record model="res.partner.canal">
+ <field name="name">phone</field>
+ </record>
+ <record model="res.partner.canal">
+ <field name="name">direct</field>
+ </record>
+ <record model="res.partner.canal">
+ <field name="name">E-mail</field>
+ </record>
+
+ <record model="res.partner.som">
+ <field name="name">Fury</field>
+ <field name="factor">1.0</field>
+ </record>
+ <record model="res.partner.som">
+ <field name="name">Unhappy</field>
+ <field name="factor">3.0</field>
+ </record>
+ <record model="res.partner.som">
+ <field name="name">Normal</field>
+ <field name="factor">5.0</field>
+ </record>
+ <record model="res.partner.som">
+ <field name="name">Happy</field>
+ <field name="factor">7.0</field>
+ </record>
+ <record model="res.partner.som">
+ <field name="name">Very Happy</field>
+ <field name="factor">8.5</field>
+ </record>
+
+</data>
+</terp>
diff --git a/bin/addons/base/res/partner/crm_view.xml b/bin/addons/base/res/partner/crm_view.xml
new file mode 100644
index 00000000000..937f529cb39
--- /dev/null
+++ b/bin/addons/base/res/partner/crm_view.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0"?>
+<terp>
+<data>
+
+ <record model="ir.ui.view" id="res_partner_canal-view">
+ <field name="name">res.partner.canal.form</field>
+ <field name="model">res.partner.canal</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Channel">
+ <field name="name" select="1"/>
+ <field name="active" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="res_partner_canal-act">
+ <field name="name">res.partner.canal.form</field>
+ <field name="res_model">res.partner.canal</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem name="Partners/Configuration/Partner events/Channels" groups="admin" action="res_partner_canal-act"/>
+
+ <record model="ir.ui.view" id="res_partner_event_type-view">
+ <field name="name">res.partner.event.type.form</field>
+ <field name="model">res.partner.event.type</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Event Type">
+ <field name="name" select="1"/>
+ <field name="active" select="1"/>
+ <field name="key" select="1" readonly="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="res_partner_event_type-act">
+ <field name="name">res.partner.event.type.form</field>
+ <field name="res_model">res.partner.event.type</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem name="Partners/Configuration/Partner events/Active partner events" groups="admin" action="res_partner_event_type-act"/>
+
+ <record model="ir.ui.view" id="res_partner_som_tree-view">
+ <field name="name">res.partner.som.tree</field>
+ <field name="model">res.partner.som</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Partner State of Mind">
+ <field name="name"/>
+ <field name="factor"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="res_partner_som-view">
+ <field name="name">res.partner.som.form</field>
+ <field name="model">res.partner.som</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Partner State of Mind">
+ <field name="name" select="1"/>
+ <field name="factor" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="res_partner_som-act">
+ <field name="name">res.partner.som.form</field>
+ <field name="res_model">res.partner.som</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem name="Partners/Configuration/Partner events/States of Mind" groups="admin" action="res_partner_som-act"/>
+
+ <record model="ir.ui.view" id="res_partner_event-wopartner-view_form">
+ <field name="name">res.partner.event.form</field>
+ <field name="model">res.partner.event</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Partner Events">
+ <separator string="General Description" colspan="4"/>
+ <field name="name" select="1"/>
+ <field name="partner_type" select="1"/>
+ <field name="som" select="1"/>
+ <field name="date" select="1"/>
+ <field name="canal_id" select="1"/>
+ <field name="type" select="1"/>
+ <field name="user_id" select="1"/>
+ <field name="probability"/>
+ <field name="planned_revenue"/>
+ <field name="planned_cost"/>
+ <separator string="Description" colspan="4"/>
+ <field name="description" colspan="3"/>
+ <separator string="Document Link" colspan="4"/>
+ <field name="document" colspan="3"/>
+ </form>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="res_partner_event-wopartner-view_tree">
+ <field name="name">res.partner.event.tree</field>
+ <field name="model">res.partner.event</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Partner Events">
+ <field name="date"/>
+ <field name="name"/>
+ <field name="som"/>
+ <field name="type"/>
+ <field name="partner_type"/>
+ <field name="user_id"/>
+ </tree>
+ </field>
+ </record>
+</data>
+</terp>
diff --git a/bin/addons/base/res/partner/partner.py b/bin/addons/base/res/partner/partner.py
new file mode 100644
index 00000000000..6485632d437
--- /dev/null
+++ b/bin/addons/base/res/partner/partner.py
@@ -0,0 +1,372 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# $Id: partner.py 1007 2005-07-25 13:18:09Z kayhman $
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import math
+
+from osv import fields,osv
+import tools
+import ir
+import pooler
+
+class res_partner_function(osv.osv):
+ _name = 'res.partner.function'
+ _description = 'Function of the contact'
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True),
+ 'code': fields.char('Code', size=8),
+ }
+ _order = 'name'
+res_partner_function()
+
+class res_country(osv.osv):
+ _name = 'res.country'
+ _description = 'Country'
+ _columns = {
+ 'name': fields.char('Country Name', size=64, help='The full name of the country.'),
+ 'code': fields.char('Country Code', size=2, help='The ISO country code in two chars. You can use this field for quick search.'),
+ }
+ _sql_constraints = [
+ ('name_uniq', 'unique (name)', 'The name of the country must be unique !'),
+ ('code_uniq', 'unique (code)', 'The code of the country must be unique !')
+ ]
+ def name_search(self, cr, user, name, args=[], operator='ilike', context={}):
+ ids = self.search(cr, user, [('code','=',name)]+ args)
+ if not ids:
+ ids = self.search(cr, user, [('name',operator,name)]+ args)
+ return self.name_get(cr, user, ids)
+ _order='code'
+res_country()
+
+class res_country_state(osv.osv):
+ _description="Country state"
+ _name = 'res.country.state'
+ _columns = {
+ 'country_id': fields.many2one('res.country', 'Country'),
+ 'name': fields.char('State Name', size=64),
+ 'code': fields.char('State Code', size=2),
+ }
+ _order = 'code'
+res_country_state()
+
+class res_payterm(osv.osv):
+ _description = 'Payment term'
+ _name = 'res.payterm'
+ _columns = {
+ 'name': fields.char('Payment term (short name)', size=64),
+ }
+res_payterm()
+
+class res_partner_category_type(osv.osv):
+ _description='Partner category types'
+ _name = 'res.partner.category.type'
+ _columns = {
+ 'name': fields.char('Category Name', required=True, size=64),
+ }
+ _order = 'name'
+res_partner_category_type()
+
+def _cat_type_get(self, cr, uid, context={}):
+ obj = self.pool.get('res.partner.category.type')
+ ids = obj.search(cr, uid, [])
+ res = obj.read(cr, uid, ids, ['name'], context)
+ return [(r['name'], r['name']) for r in res]
+
+class res_partner_category(osv.osv):
+ def name_get(self, cr, uid, ids, context={}):
+ if not len(ids):
+ return []
+ reads = self.read(cr, uid, ids, ['name','parent_id'], context)
+ res = []
+ for record in reads:
+ name = record['name']
+ if record['parent_id']:
+ name = record['parent_id'][1]+' / '+name
+ res.append((record['id'], name))
+ return res
+
+ def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
+ res = self.name_get(cr, uid, ids)
+ return dict(res)
+ def _check_recursion(self, cr, uid, ids):
+ level = 100
+ while len(ids):
+ cr.execute('select distinct parent_id from res_partner_category where id in ('+','.join(map(str,ids))+')')
+ ids = filter(None, map(lambda x:x[0], cr.fetchall()))
+ if not level:
+ return False
+ level -= 1
+ return True
+
+ _description='Partner Categories'
+ _name = 'res.partner.category'
+ _columns = {
+ 'name': fields.char('Category Name', required=True, size=64),
+ 'type_id': fields.many2one('res.partner.category.type', 'Category Type'),
+ 'parent_id': fields.many2one('res.partner.category', 'Parent Category', select=True),
+ 'complete_name': fields.function(_name_get_fnc, method=True, type="char", string='Name'),
+ 'child_ids': fields.one2many('res.partner.category', 'parent_id', 'Childs Category'),
+ 'active' : fields.boolean('Active'),
+ }
+ _constraints = [
+ (_check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
+ ]
+ _defaults = {
+ 'active' : lambda *a: 1,
+ }
+ _order = 'parent_id,name'
+res_partner_category()
+
+class res_partner_title(osv.osv):
+ _name = 'res.partner.title'
+ _columns = {
+ 'name': fields.char('Title', required=True, size=46, translate=True),
+ 'shortcut': fields.char('Shortcut', required=True, size=16),
+ 'domain': fields.selection([('partner','Partner'),('contact','Contact')], 'Domain', required=True, size=24)
+ }
+ _order = 'name'
+res_partner_title()
+
+def _contact_title_get(self, cr, uid, context={}):
+ obj = self.pool.get('res.partner.title')
+ ids = obj.search(cr, uid, [('domain', '=', 'contact')])
+ res = obj.read(cr, uid, ids, ['shortcut','name'], context)
+ return [(r['shortcut'], r['name']) for r in res]
+
+def _partner_title_get(self, cr, uid, context={}):
+ obj = self.pool.get('res.partner.title')
+ ids = obj.search(cr, uid, [('domain', '=', 'partner')])
+ res = obj.read(cr, uid, ids, ['shortcut','name'], context)
+ return [(r['shortcut'], r['name']) for r in res]
+
+def _lang_get(self, cr, uid, context={}):
+ obj = self.pool.get('res.lang')
+ ids = obj.search(cr, uid, [])
+ res = obj.read(cr, uid, ids, ['code', 'name'], context)
+ res = [(r['code'], r['name']) for r in res]
+ return res + [(False, '')]
+
+class res_partner(osv.osv):
+ _description='Partner'
+ _name = "res.partner"
+ _order = "name"
+ _columns = {
+ 'name': fields.char('Name', size=128, required=True, select=True),
+ 'date': fields.date('Date'),
+ 'title': fields.selection(_partner_title_get, 'Title', size=32),
+ 'parent_id': fields.many2one('res.partner','Main Company', select=True),
+ 'child_ids': fields.one2many('res.partner', 'parent_id', 'Partner Ref.'),
+ 'ref': fields.char('Code', size=64),
+ 'lang': fields.selection(_lang_get, 'Language', size=5),
+ 'user_id': fields.many2one('res.users', 'Salesman'),
+ 'responsible': fields.many2one('res.users', 'Users'),
+ 'vat': fields.char('VAT',size=32),
+ 'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'),
+ 'website': fields.char('Website',size=64),
+ 'comment': fields.text('Notes'),
+ 'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'),
+ 'category_id': fields.many2many('res.partner.category', 'res_partner_category_rel', 'partner_id', 'category_id', 'Categories'),
+ 'events': fields.one2many('res.partner.event', 'partner_id', 'events'),
+ 'credit_limit': fields.float(string='Credit Limit'),
+ 'ean13': fields.char('EAN13', size=13),
+ 'active': fields.boolean('Active'),
+ }
+ _defaults = {
+ 'active': lambda *a: 1,
+ }
+ _sql_constraints = [
+ ('name_uniq', 'unique (name)', 'The name of the partner must be unique !')
+ ]
+
+ def _check_ean_key(self, cr, uid, ids):
+ for partner_o in pooler.get_pool(cr.dbname).get('res.partner').read(cr, uid, ids, ['ean13',]):
+ thisean=partner_o['ean13']
+ if thisean and thisean!='':
+ if len(thisean)!=13:
+ return False
+ sum=0
+ for i in range(12):
+ if not (i % 2):
+ sum+=int(thisean[i])
+ else:
+ sum+=3*int(thisean[i])
+ if math.ceil(sum/10.0)*10-sum!=int(thisean[12]):
+ return False
+ return True
+
+# _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
+
+ def name_get(self, cr, uid, ids, context={}):
+ if not len(ids):
+ return []
+ if context.get('show_ref', False):
+ rec_name = 'ref'
+ else:
+ rec_name = 'name'
+
+ res = [(r['id'], r[rec_name]) for r in self.read(cr, uid, ids, [rec_name], context)]
+ return res
+
+ def name_search(self, cr, uid, name, args=[], operator='ilike', context={}, limit=80):
+ if name:
+ ids = self.search(cr, uid, [('ref', '=', name)] + args, limit=limit)
+ if not ids:
+ ids = self.search(cr, uid, [('name', operator, name)] + args, limit=limit)
+ else:
+ ids = self.search(cr, uid, args, limit=limit)
+ return self.name_get(cr, uid, ids, context)
+
+ def _email_send(self, cr, uid, ids, email_from, subject, body, on_error=None):
+ partners = self.browse(cr, uid, ids)
+ for partner in partners:
+ if len(partner.address):
+ if partner.address[0].email:
+ tools.email_send(email_from, [partner.address[0].email], subject, body, on_error)
+ return True
+
+ def email_send(self, cr, uid, ids, email_from, subject, body, on_error=''):
+ while len(ids):
+ self.pool.get('ir.cron').create(cr, uid, {
+ 'name': 'Send Partner Emails',
+ 'user_id': uid,
+# 'nextcall': False,
+ 'model': 'res.partner',
+ 'function': '_email_send',
+ 'args': repr([ids[:16], email_from, subject, body, on_error])
+ })
+ ids = ids[16:]
+ return True
+
+ def address_get(self, cr, uid, ids, adr_pref=['default']):
+ cr.execute('select type,id from res_partner_address where partner_id in ('+','.join(map(str,ids))+')')
+ res = cr.fetchall()
+ adr = dict(res)
+ # get the id of the (first) default address if there is one,
+ # otherwise get the id of the first address in the list
+ if res:
+ default_address = adr.get('default', res[0][1])
+ else:
+ default_address = False
+ result = {}
+ for a in adr_pref:
+ result[a] = adr.get(a, default_address)
+ return result
+
+ def gen_next_ref(self, cr, uid, ids):
+ if len(ids) != 1:
+ return True
+
+ # compute the next number ref
+ cr.execute("select ref from res_partner where ref is not null order by char_length(ref) desc, ref desc limit 1")
+ res = cr.dictfetchall()
+ ref = res and res[0]['ref'] or '0'
+ try:
+ nextref = int(ref)+1
+ except e:
+ raise osv.except_osv('Warning', "Couldn't generate the next id because some partners have an alphabetic id !")
+
+ # update the current partner
+ cr.execute("update res_partner set ref=%d where id=%d", (nextref, ids[0]))
+ return True
+res_partner()
+
+class res_partner_bank(osv.osv):
+ _description='Bank Details'
+ _name = "res.partner.bank"
+ _order = "sequence"
+ _columns = {
+ 'name': fields.char('Account Name', size=64, required=True),
+ 'sequence': fields.integer('Sequence'),
+ 'iban': fields.char('Account number', size=64),
+ 'swift': fields.char('Swift', size=64),
+ 'bank_name': fields.char('Bank Name', size=64),
+ 'bank_guichet': fields.char('Branch', size=64),
+ 'partner_id': fields.many2one('res.partner', 'Partner', required=True, ondelete='cascade', select=True),
+ 'active': fields.boolean('Active'),
+ }
+ _defaults = {
+ 'active': lambda *a: 1,
+ }
+res_partner_bank()
+
+
+class res_partner_address(osv.osv):
+ _description ='Partner Contact'
+ _name = 'res.partner.address'
+ _order = 'id'
+ _columns = {
+ 'partner_id': fields.many2one('res.partner', 'Partner', required=True, ondelete='cascade', select=True),
+ 'type': fields.selection( [ ('default','Default'),('invoice','Invoice'), ('delivery','Delivery'), ('contact','Contact'), ('other','Other') ],'Address Type'),
+ 'function': fields.many2one('res.partner.function', 'Function', relate=True),
+ 'title': fields.selection(_contact_title_get, 'Title', size=32),
+ 'name': fields.char('Contact Name', size=64),
+ 'street': fields.char('Street', size=128),
+ 'street2': fields.char('Street2', size=128),
+ 'zip': fields.char('Zip', change_default=True, size=24),
+ 'city': fields.char('City', size=128),
+ 'state_id': fields.many2one("res.country.state", 'State', domain="[('country_id','=',country_id)]"),
+ 'country_id': fields.many2one('res.country', 'Country'),
+ 'email': fields.char('E-Mail', size=64),
+ 'phone': fields.char('Phone', size=64),
+ 'fax': fields.char('Fax', size=64),
+ 'mobile': fields.char('Mobile', size=64),
+ 'birthdate': fields.char('Birthdate', size=64),
+ 'active': fields.boolean('Active'),
+ }
+ _defaults = {
+ 'active': lambda *a: 1,
+ }
+
+ def name_get(self, cr, user, ids, context={}):
+ if not len(ids):
+ return []
+ res = []
+ for r in self.read(cr, user, ids, ['name','zip','city','partner_id']):
+ if context.get('contact_display', 'contact')=='partner':
+ res.append((r['id'], r['partner_id'][1]))
+ else:
+ addr = str(r['name'] or '')
+ if r['name'] and (r['zip'] or r['city']):
+ addr += ', '
+ addr += str(r['zip'] or '') + ' ' + str(r['city'] or '')
+ res.append((r['id'], addr))
+ return res
+
+ def name_search(self, cr, user, name, args=[], operator='ilike', context={}, limit=80):
+ if context.get('contact_display', 'contact')=='partner':
+ ids = self.search(cr, user, [('partner_id',operator,name)], limit=limit)
+ else:
+ ids = self.search(cr, user, [('zip','=',name)] + args, limit=limit)
+ if not ids:
+ ids = self.search(cr, user, [('city',operator,name)] + args, limit=limit)
+ if not ids:
+ ids = self.search(cr, user, [('name',operator,name)] + args, limit=limit)
+ return self.name_get(cr, user, ids)
+res_partner_address()
+
+# vim:noexpandtab:
diff --git a/bin/addons/base/res/partner/partner_data.xml b/bin/addons/base/res/partner/partner_data.xml
new file mode 100644
index 00000000000..82fcfae9607
--- /dev/null
+++ b/bin/addons/base/res/partner/partner_data.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<terp>
+<data>
+</data>
+</terp>
diff --git a/bin/addons/base/res/partner/partner_demo.xml b/bin/addons/base/res/partner/partner_demo.xml
new file mode 100644
index 00000000000..561821146c8
--- /dev/null
+++ b/bin/addons/base/res/partner/partner_demo.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0"?>
+<terp>
+<data noupdate="1">
+
+ #
+ # Resource: res.partner.title
+ #
+
+<record id="res_partner_title_madam" model="res.partner.title">
+ <field name="domain">contact</field>
+ <field name="name">Madam</field>
+ <field name="shortcut">Ms.</field>
+</record>
+<record id="res_partner_title_miss" model="res.partner.title">
+ <field name="domain">contact</field>
+ <field name="name">Miss</field>
+ <field name="shortcut">Mss</field>
+</record>
+<record id="res_partner_title_sir" model="res.partner.title">
+ <field name="domain">contact</field>
+ <field name="name">Sir</field>
+ <field name="shortcut">M.</field>
+</record>
+
+
+ #
+ # Resource: res.partner.category
+ #
+
+<record id="res_partner_category_0" model="res.partner.category">
+ <field name="name">Customer</field>
+</record>
+<record id="res_partner_category_1" model="res.partner.category">
+ <field name="name">Prospect</field>
+</record>
+<record id="res_partner_category_2" model="res.partner.category">
+ <field name="name">Tiny ERP Partners</field>
+ <field name="parent_id" ref="res_partner_category_0"/>
+</record>
+<record id="res_partner_category_3" model="res.partner.category">
+ <field name="name">Starter Partner</field>
+ <field name="parent_id" ref="res_partner_category_2"/>
+</record>
+<record id="res_partner_category_4" model="res.partner.category">
+ <field name="name">Basic Partner</field>
+ <field name="parent_id" ref="res_partner_category_2"/>
+</record>
+<record id="res_partner_category_5" model="res.partner.category">
+ <field name="name">Gold Partner</field>
+ <field name="parent_id" ref="res_partner_category_2"/>
+</record>
+<record id="res_partner_category_7" model="res.partner.category">
+ <field name="name">Openstuff.net</field>
+ <field name="parent_id" ref="res_partner_category_0"/>
+</record>
+<record id="res_partner_category_12" model="res.partner.category">
+ <field name="name">Segmentation</field>
+ <field name="parent_id" ref="res_partner_category_0"/>
+</record>
+<record id="res_partner_category_13" model="res.partner.category">
+ <field name="name">Important customers</field>
+ <field name="parent_id" ref="res_partner_category_12"/>
+</record>
+<record id="res_partner_category_14" model="res.partner.category">
+ <field name="name">Bad customers</field>
+ <field name="parent_id" ref="res_partner_category_12"/>
+</record>
+
+<record id="res_partner_category_8" model="res.partner.category">
+ <field name="name">Supplier</field>
+</record>
+<record id="res_partner_category_9" model="res.partner.category">
+ <field name="name">Components Supplier</field>
+ <field name="parent_id" ref="res_partner_category_8"/>
+</record>
+<record id="res_partner_category_10" model="res.partner.category">
+ <field name="name">Open Source Service Company</field>
+ <field name="parent_id" ref="res_partner_category_8"/>
+</record>
+<record id="res_partner_category_11" model="res.partner.category">
+ <field name="name">Textile Suppliers</field>
+ <field name="parent_id" ref="res_partner_category_8"/>
+</record>
+
+
+ #
+ # Resource: res.partner
+ #
+
+<record id="res_partner_asus" model="res.partner">
+ <field name="name">ASUStek</field>
+</record>
+<record id="res_partner_agrolait" model="res.partner">
+ <field name="name">Agrolait</field>
+</record>
+<record id="res_partner_2" model="res.partner">
+ <field name="name">Bank Wealthy and sons</field>
+</record>
+<record id="res_partner_3" model="res.partner">
+ <field name="lang">en</field>
+ <field name="name">China Export</field>
+</record>
+<record id="res_partner_4" model="res.partner">
+ <field name="name">Distrib PC</field>
+</record>
+<record id="res_partner_5" model="res.partner">
+ <field name="name">Ecole de Commerce de Liege</field>
+</record>
+<record id="res_partner_6" model="res.partner">
+ <field name="name">Elec Import</field>
+</record>
+<record id="res_partner_maxtor" model="res.partner">
+ <field name="name">Maxtor</field>
+</record>
+<record id="res_partner_seagate" model="res.partner">
+ <field name="name">Seagate</field>
+</record>
+<record id="res_partner_8" model="res.partner">
+ <field name="website">http://mediapole.net</field>
+ <field name="name">Mediapole SPRL</field>
+</record>
+<record id="res_partner_9" model="res.partner">
+ <field name="website">http://opensides.be</field>
+ <field name="name">Opensides sprl</field>
+ <field name="ref">os</field>
+</record>
+<record id="res_partner_10" model="res.partner">
+ <field name="name">Tecsas</field>
+ <field name="ean13">3020170000003</field>
+</record>
+<record id="res_partner_11" model="res.partner">
+ <field name="name">Leclerc</field>
+</record>
+<record id="res_partner_14" model="res.partner">
+ <field name="name">Centrale d'achats BML</field>
+ <field name="ean13">3020178572427</field>
+ <field name="parent_id" ref="res_partner_10"/>
+</record>
+<record id="res_partner_15" model="res.partner">
+ <field name="name">Magazin BML 1</field>
+ <field name="ean13">3020178570171</field>
+ <field name="parent_id" ref="res_partner_14"/>
+</record>
+
+ #
+ # Resource: res.partner.address
+ #
+
+<record id="res_partner_address_1" model="res.partner.address">
+ <field name="city">Bruxelles</field>
+ <field name="name">Benoit Mortier</field>
+ <field name="zip">1030</field>
+ <field name="country_id" model="res.country" search="[('name','=','Belgium')]"/>
+ <field name="email">info@opensides.be</field>
+ <field name="phone">(+32)2 211 34 83</field>
+ <field name="street">Rue des Palais 44, bte 33</field>
+ <field name="type">default</field>
+ <field name="partner_id" ref="res_partner_9"/>
+</record>
+<record id="res_partner_address_2" model="res.partner.address">
+ <field name="city">Avignon CEDEX 09</field>
+ <field name="name">Laurent Jacot</field>
+ <field name="zip">84911</field>
+ <field name="country_id" model="res.country" search="[('name','=','France')]"/>
+ <field name="email">contact@tecsas.fr</field>
+ <field name="phone">(+33)4.32.74.10.57</field>
+ <field name="street">85 rue du traite de Rome</field>
+ <field name="type">default</field>
+ <field name="partner_id" ref="res_partner_10"/>
+</record>
+<record id="res_partner_address_3" model="res.partner.address">
+ <field name="city">Louvain-la-Neuve</field>
+ <field name="name">Thomas Passot</field>
+ <field name="zip">1348</field>
+ <field name="country_id" model="res.country" search="[('name','=','Belgium')]"/>
+ <field name="email">info@mediapole.net</field>
+ <field name="phone">(+32).10.45.17.73</field>
+ <field name="street">Rue de l'Angelique, 1</field>
+ <field name="partner_id" ref="res_partner_8"/>
+</record>
+<record id="res_partner_address_tang" model="res.partner.address">
+ <field name="city">Taiwan</field>
+ <field name="name">Tang</field>
+ <field name="zip">23410</field>
+ <field name="title">M.</field>
+ <field name="country_id" model="res.country" search="[('name','=','Taiwan')]"/>
+ <field name="street">31 Hong Kong street</field>
+ <field name="type">default</field>
+ <field name="partner_id" ref="res_partner_asus"/>
+</record>
+<record id="res_partner_address_wong" model="res.partner.address">
+ <field name="city">Hong Kong</field>
+ <field name="name">Wong</field>
+ <field name="zip">23540</field>
+ <field name="title">M.</field>
+ <field name="country_id" model="res.country" search="[('name','=','China')]"/>
+ <field name="street">56 Beijing street</field>
+ <field name="type">default</field>
+ <field name="partner_id" ref="res_partner_maxtor"/>
+</record>
+<record id="res_partner_address_6" model="res.partner.address">
+ <field name="city">Bruxelles</field>
+ <field name="name">Etienne Lacarte</field>
+ <field name="zip">2365</field>
+ <field name="title">M.</field>
+ <field name="country_id" model="res.country" search="[('name','=','Belgium')]"/>
+ <field name="street">23 rue du Vieux Bruges</field>
+ <field name="type">default</field>
+ <field name="partner_id" ref="res_partner_6"/>
+</record>
+<record id="res_partner_address_7" model="res.partner.address">
+ <field name="city">Namur</field>
+ <field name="name">Jean Guy Lavente</field>
+ <field name="zip">2541</field>
+ <field name="title">M.</field>
+ <field name="country_id" model="res.country" search="[('name','=','Belgium')]"/>
+ <field name="street">42 rue de la Lesse</field>
+ <field name="type">default</field>
+ <field name="partner_id" ref="res_partner_4"/>
+</record>
+<record id="res_partner_address_8" model="res.partner.address">
+ <field name="city">Wavre</field>
+ <field name="name">Sylvie Lelitre</field>
+ <field name="zip">5478</field>
+ <field name="title">Ms.</field>
+ <field name="country_id" model="res.country" search="[('name','=','Belgium')]"/>
+ <field name="street">69 rue de Chimay</field>
+ <field name="type">default</field>
+ <field name="partner_id" ref="res_partner_agrolait"/>
+</record>
+<record id="res_partner_address_9" model="res.partner.address">
+ <field name="city">Paris</field>
+ <field name="name">Arthur Grosbonnet</field>
+ <field name="zip">75016</field>
+ <field name="title">M.</field>
+ <field name="country_id" model="res.country" search="[('name','=','France')]"/>
+ <field name="street">1 rue Rockfeller</field>
+ <field name="type">default</field>
+ <field name="partner_id" ref="res_partner_2"/>
+</record>
+<record id="res_partner_address_10" model="res.partner.address">
+ <field name="city">Liege</field>
+ <field name="name">Karine Lesbrouffe</field>
+ <field name="zip">6985</field>
+ <field name="title">Mss</field>
+ <field name="country_id" model="res.country" search="[('name','=','Belgium')]"/>
+ <field name="street">2 Impasse de la Soif</field>
+ <field name="type">default</field>
+ <field name="partner_id" ref="res_partner_5"/>
+</record>
+<record id="res_partner_address_zen" model="res.partner.address">
+ <field name="city">Shanghai</field>
+ <field name="name">Zen</field>
+ <field name="zip">4785552</field>
+ <field name="title">M.</field>
+ <field name="country_id" model="res.country" search="[('name','=','China')]"/>
+ <field name="street">52 Chop Suey street</field>
+ <field name="type">default</field>
+ <field name="partner_id" ref="res_partner_3"/>
+</record>
+<record id="res_partner_address_12" model="res.partner.address">
+ <field name="type">default</field>
+ <field name="name">Centrale</field>
+ <field name="partner_id" ref="res_partner_10"/>
+</record>
+<record id="res_partner_address_13" model="res.partner.address">
+ <field name="type">default</field>
+ <field name="name">Centrale d'achats 1</field>
+ <field name="partner_id" ref="res_partner_14"/>
+</record>
+<record id="res_partner_address_14" model="res.partner.address">
+ <field name="type">default</field>
+ <field name="name">Shop 1</field>
+ <field name="partner_id" ref="res_partner_15"/>
+</record>
+<record id="res_partner_address_15" model="res.partner.address">
+ <field name="type">default</field>
+ <field name="name">Shop 2</field>
+ <field name="partner_id" ref="res_partner_11"/>
+</record>
+<record id="res_partner_address_16" model="res.partner.address">
+ <field name="type">default</field>
+ <field name="name">Shop 3</field>
+ <field name="partner_id" ref="res_partner_11"/>
+</record>
+
+</data>
+</terp>
diff --git a/bin/addons/base/res/partner/partner_report.xml b/bin/addons/base/res/partner/partner_report.xml
new file mode 100644
index 00000000000..5d69e4fbbca
--- /dev/null
+++ b/bin/addons/base/res/partner/partner_report.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<terp>
+ <data>
+ <report string="Labels" model="res.partner" name="res.partner.address" xml="base/res/partner/report/partner_address.xml" xsl="base/res/partner/report/partner_address.xsl"/>
+ <!--
+ <report string="Business Cards" model="res.partner" name="res.partner.businesscard" xml="base/res/partner/report/business_card.xml" xsl="base/res/partner/report/business_card.xsl"/>
+ -->
+ </data>
+</terp>
diff --git a/bin/addons/base/res/partner/partner_view.xml b/bin/addons/base/res/partner/partner_view.xml
new file mode 100644
index 00000000000..31b9f750236
--- /dev/null
+++ b/bin/addons/base/res/partner/partner_view.xml
@@ -0,0 +1,504 @@
+<?xml version="1.0"?>
+<terp>
+<data>
+ <menuitem name="Partners" sequence="0" icon="terp-partner" id="menu_base_partner"/>
+ <menuitem name="Partners/Configuration" groups="admin"/>
+
+ <!--
+ ================================
+ Function
+ ================================
+ -->
+ <record model="ir.ui.view" id="view_partner_function_form">
+ <field name="name">res.partner.function.form</field>
+ <field name="model">res.partner.function</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Partner Functions">
+ <field name="name" select="1"/>
+ <field name="code" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_partner_function_form">
+ <field name="name">res.partner.function.form</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.partner.function</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Partners/Configuration/Functions" groups="admin" action="action_partner_function_form" id="menu_partner_function_form"/>
+
+ <!--
+ ================================
+ Country
+ ================================
+ -->
+ <record model="ir.ui.view" id="view_country_tree">
+ <field name="name">res.country.tree</field>
+ <field name="model">res.country</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Country">
+ <field name="name" />
+ <field name="code"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_country_form">
+ <field name="name">res.country.form</field>
+ <field name="model">res.country</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Country">
+ <field name="name" select="1"/>
+ <field name="code" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_country">
+ <field name="name">res.country.form</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.country</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Partners/Configuration/Localisation/Countries" groups="admin" action="action_country" id="menu_country"/>
+
+ <!--
+ ================================
+ State
+ ================================
+ -->
+ <record model="ir.ui.view" id="view_country_state_tree">
+ <field name="name">res.country.state.tree</field>
+ <field name="model">res.country.state</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="State">
+ <field name="country_id"/>
+ <field name="name"/>
+ <field name="code"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_country_state_form">
+ <field name="name">res.country.state.form</field>
+ <field name="model">res.country.state</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="State">
+ <field name="country_id" select="1"/>
+ <field name="name" select="1"/>
+ <field name="code" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_country_state">
+ <field name="name">res.country.state</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.country.state</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_country_state_form"/>
+ </record>
+ <menuitem name="Partners/Configuration/Localisation/States" groups="admin" action="action_country_state" id="menu_country_state"/>
+
+ <!--
+ =====================
+ Partner Address
+ =====================
+ -->
+ <record model="ir.ui.view" id="view_partner_address_tree">
+ <field name="name">res.partner.address.tree</field>
+ <field name="model">res.partner.address</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Partner Contacts">
+ <field name="partner_id" />
+ <field name="name" />
+ <field name="zip"/>
+ <field name="city"/>
+ <field name="country_id"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_partner_addess_tree">
+ <field name="name">res.partner.address.tree</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.partner.address</field>
+ <field name="view_type">tree</field>
+ <field name="view_id" ref="view_partner_address_tree"/>
+ </record>
+
+ <record model="ir.ui.view" id="view_partner_address_form1">
+ <field name="name">res.partner.address.form1</field>
+ <field name="model">res.partner.address</field>
+ <field name="type">form</field>
+ <field name="priority" eval="8"/>
+ <field name="arch" type="xml">
+ <form string="Partner Contacts">
+ <field name="partner_id" colspan="3" select="1"/>
+ <newline/>
+ <field name="name" select="1"/>
+ <field name="title" domain="[('domain', '=', 'contact')]"/>
+ <field name="function"/>
+ <field name="type" select="1"/>
+ <field name="street" select="1"/>
+ <field name="street2"/>
+ <newline/>
+ <field name="zip" select="1"/>
+ <field name="city" select="1"/>
+ <newline/>
+ <field name="country_id" select="1"/>
+ <field name="state_id" select="1"/>
+ <newline/>
+ <field name="phone" select="1"/>
+ <field name="fax"/>
+ <newline/>
+ <field name="mobile" select="1"/>
+ <field name="email" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_partner_address_form">
+ <field name="name">res.partner.address</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.partner.address</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_partner_address_form1"/>
+ </record>
+ <menuitem name="Partners/Partner Contacts" action="action_partner_address_form" id="menu_partner_address_form"/>
+
+ <!--
+ =========================================
+ the short form used in the partner form
+ =========================================
+ -->
+ <record model="ir.ui.view" id="view_partner_address_form2">
+ <field name="name">res.partner.address.form2</field>
+ <field name="model">res.partner.address</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Contacts">
+ <field name="name" select="1"/>
+ <field name="title" domain="[('domain', '=', 'contact')]"/>
+ <field name="street"/>
+ <field name="street2"/>
+ <field name="type" select="1"/>
+ <field name="function"/>
+ <newline/>
+ <field name="zip" select="1"/>
+ <field name="city" select="1"/>
+ <newline/>
+ <field name="country_id" select="1"/>
+ <field name="state_id" select="1"/>
+ <newline/>
+ <field name="phone"/>
+ <field name="fax"/>
+ <newline/>
+ <field name="mobile"/>
+ <field name="email"/>
+ </form>
+ </field>
+ </record>
+
+ <!--
+ =======================
+ Partner Titles
+ =======================
+ -->
+ <record model="ir.ui.view" id="view_partner_title_form">
+ <field name="name">res.partner.title.form</field>
+ <field name="model">res.partner.title</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Partners Titles">
+ <field name="name" select="1"/>
+ <field name="shortcut" select="1"/>
+ <field name="domain"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_partner_title">
+ <field name="name">res.partner.title</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.partner.title</field>
+ <field name="view_type">form</field>
+ </record>
+ <menuitem name="Partners/Configuration/Titles" groups="admin" action="action_partner_title" id="menu_partner_title"/>
+
+ <!--
+ =======================
+ Partner
+ =======================
+ -->
+ <record model="ir.ui.view" id="view_partner_tree">
+ <field name="name">res.partner.tree</field>
+ <field name="model">res.partner</field>
+ <field name="type">tree</field>
+ <field name="priority" eval="8"/>
+ <field name="arch" type="xml">
+ <tree string="Partners">
+ <field name="name"/>
+ <field name="ref"/>
+ <field name="address"/>
+ <field name="lang"/>
+ <field name="category_id"/>
+ <field name="vat"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_partner_form">
+ <field name="name">res.partner.form</field>
+ <field name="model">res.partner</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Partners">
+ <notebook>
+ <page string="General">
+ <separator string="General Information" colspan="4"/>
+ <field name="name" select="1"/>
+ <field name="title" domain="[('domain', '=', 'partner')]"/>
+ <field name="ref" select="1"/>
+ <field name="lang" select="1"/>
+ <newline/>
+ <field name="address" mode="form,tree" colspan="4" nolabel="1" select="1">
+ <form string="Partner Contacts">
+ <field name="name" select="1"/>
+ <field name="title" domain="[('domain', '=', 'contact')]"/>
+ <field name="function"/>
+ <field name="type" select="1"/>
+ <field name="street" select="1"/>
+ <field name="street2"/>
+ <newline/>
+ <field name="zip" select="1"/>
+ <field name="city" select="1"/>
+ <newline/>
+ <field name="country_id" select="1" completion="1"/>
+ <field name="state_id" select="1"/>
+ <newline/>
+ <field name="phone"/>
+ <field name="fax"/>
+ <newline/>
+ <field name="mobile"/>
+ <field name="email" select="1" widget="email" />
+ </form>
+ </field>
+ <separator string="Categories" colspan="4"/>
+ <field name="category_id" colspan="4" select="1" nolabel="1"/>
+ </page>
+ <page string="Extra Info">
+ <field name="vat" select="1"/>
+ <field name="active" select="1" />
+ <field name="credit_limit" select="1"/>
+ <field name="user_id" select="1"/>
+ <field name="parent_id" select="1"/>
+ <field name="website" widget="url"/>
+ <field name="date" select="1"/>
+ <field name="bank_ids" colspan="4" nolabel="1"/>
+ <separator string="Notes" colspan="4"/>
+ <field name="comment" colspan="4" nolabel="1"/>
+ </page>
+ <page string="Event History">
+ <field name="events" colspan="4" nolabel="1" widget="one2many_list"/>
+ </page>
+ <page string="Properties">
+ <properties/>
+ </page>
+ </notebook>
+ </form>
+ </field>
+ </record>
+
+ <record model="ir.actions.act_window" id="action_partner_form">
+ <field name="name">res.partner</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.partner</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_partner_form"/>
+ </record>
+ <menuitem name="Partners/Partners" action="action_partner_form" id="menu_partner_form"/>
+
+ <record model="ir.ui.view" id="view_payterm_form">
+ <field name="name">res.payterm</field>
+ <field name="model">res.payterm</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Payment term">
+ <field name="name" colspan="4" />
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_payterm_form">
+ <field name="name">res.payterm</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.payterm</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_payterm_form"/>
+ </record>
+
+ <record model="ir.ui.view" id="view_partner_bank_form">
+ <field name="name">res.partner.bank.form</field>
+ <field name="model">res.partner.bank</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Bank Details">
+ <field name="name" select="1" colspan="3"/>
+ <field name="sequence"/>
+ <field name="active" select="1"/>
+ <field name="iban" select="1"/>
+ <field name="swift" select="1"/>
+ <field name="bank_name" select="1"/>
+ <field name="bank_guichet" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_partner_bank_tree">
+ <field name="name">res.partner.bank.tree</field>
+ <field name="model">res.partner.bank</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Bank Details">
+ <field name="sequence"/>
+ <field name="name"/>
+ <field name="iban"/>
+ <field name="swift"/>
+ </tree>
+ </field>
+ </record>
+
+ <!--
+ ======================
+ Company Architecture
+ ======================
+ -->
+ <record model="ir.ui.view" id="view_partner_tree2">
+ <field name="name">res.partner.tree</field>
+ <field name="model">res.partner</field>
+ <field name="type">tree</field>
+ <field name="field_parent">child_ids</field>
+ <field name="arch" type="xml">
+ <tree string="Partners">
+ <field name="name"/>
+ <field name="lang"/>
+ <field name="ref"/>
+ <field name="vat"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action2">
+ <field name="name">Company Architecture</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.partner</field>
+ <field name="domain">[('id','in',active_ids)]</field>
+ <field name="view_type">tree</field>
+ <field name="view_id" ref="view_partner_tree2"/>
+ </record>
+ <record model="ir.values" id="ir_partner_structure">
+ <field name="key2" eval="'client_action_multi'"/>
+ <field name="model" eval="'res.partner'"/>
+ <field name="name">Company Architecture</field>
+ <field name="value" eval="'ir.actions.act_window,'+str(action2)"/>
+ <field name="object" eval="True"/>
+ </record>
+
+ <!--
+ ======================
+ Categories
+ ======================
+ -->
+ <record model="ir.ui.view" id="view_partner_category_form">
+ <field name="name">res.partner.category.form</field>
+ <field name="model">res.partner.category</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Partner Categories">
+ <field name="name" select="1"/>
+ <field name="active" select="1" />
+ <field name="type_id" select="1" />
+ <field name="parent_id"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_partner_category_list">
+ <field name="name">res.partner.category.list</field>
+ <field name="model">res.partner.category</field>
+ <field name="type">tree</field>
+ <field name="priority" eval="6"/>
+ <field name="arch" type="xml">
+ <tree string="Partner Categories">
+ <field name="complete_name"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_partner_category_tree">
+ <field name="name">res.partner.category.tree</field>
+ <field name="model">res.partner.category</field>
+ <field name="type">tree</field>
+ <field name="field_parent">child_ids</field>
+ <field name="arch" type="xml">
+ <tree string="Partner Categories">
+ <field name="name"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_partner_category">
+ <field name="name">res.partner.category</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.partner.category</field>
+ <field name="view_type">tree</field>
+ <field name="view_id" ref="view_partner_category_tree"/>
+ <field name="domain">[('parent_id','=',False)]</field>
+ </record>
+ <menuitem name="Partners/Configuration/Categories" action="action_partner_category" id="menu_partner_category"/>
+
+ <record model="ir.actions.act_window" id="action_partner_category_form">
+ <field name="name">res.partner.category</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.partner.category</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_partner_category_form"/>
+ </record>
+ <menuitem name="Partners/Configuration/Categories/Edit Categories" action="action_partner_category_form" id="menu_partner_category_form"/>
+
+
+ <record model="ir.ui.view" id="view_partner_category_type_form">
+ <field name="name">res.partner.category.type.form</field>
+ <field name="model">res.partner.category.type</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Partner Type of Categories">
+ <field name="name" select="1"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_partner_category_type_form">
+ <field name="name">res.partner.category.type.form</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.partner.category.type</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem name="Partners/Configuration/Categories/Type of Categories" groups="admin" action="action_partner_category_type_form" id="menu_partner_category_type_form"/>
+
+ # Admin config
+
+ <menuitem name="Administration/Configuration/Partner" sequence="3"/>
+
+ <record model="ir.actions.act_window" id="open_module_type_partenaire">
+ <field name="name">res.partner.category</field>
+ <field name="res_model">res.partner.category</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem name="Administration/Configuration/Partner/Define Partners categories" action="open_module_type_partenaire" sequence="1" id="menu_type_partenaire"/>
+
+ <record model="ir.actions.act_window" id="open_module_def_partenaire">
+ <field name="name">res.partner</field>
+ <field name="res_model">res.partner</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem name="Administration/Configuration/Partner/Define Partners" action="open_module_def_partenaire" sequence="2" id="menu_partenaire_def"/>
+
+</data>
+</terp>
diff --git a/bin/addons/base/res/partner/partner_wizard.xml b/bin/addons/base/res/partner/partner_wizard.xml
new file mode 100644
index 00000000000..3a01ecce588
--- /dev/null
+++ b/bin/addons/base/res/partner/partner_wizard.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<terp>
+ <data>
+ <wizard string="Send SMS" model="res.partner" name="res.partner.sms_send"/>
+ <wizard string="Mass Mailing" model="res.partner" name="res.partner.spam_send"/>
+ <!--
+ <wizard string="Check EAN13" model="res.partner" name="res.partner.ean13"/>
+ <wizard string="Clear IDs" model="res.partner" name="res.partner.clear_ids"/>
+ -->
+ </data>
+</terp>
diff --git a/bin/addons/base/res/partner/report/__init__.py b/bin/addons/base/res/partner/report/__init__.py
new file mode 100644
index 00000000000..b8d498ab3b5
--- /dev/null
+++ b/bin/addons/base/res/partner/report/__init__.py
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+#from report import report_sxw
+#report_sxw.report_sxw('report.partner.list', 'res.partner', 'addons/base/res/partner/report/partner_list.rml')
+
+
diff --git a/bin/addons/base/res/partner/report/business_card.xml b/bin/addons/base/res/partner/report/business_card.xml
new file mode 100644
index 00000000000..d08f99d936c
--- /dev/null
+++ b/bin/addons/base/res/partner/report/business_card.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<addresses>
+ <address type="fields" name="id">
+ <name type="field" name="name"/>
+ <role type="field" name="title"/>
+ <phone type="field" name="phone"/>
+ <email type="field" name="email"/>
+ <mobile type="field" name="mobile"/>
+ <company>Tiny sprl</company>
+ <street>Rue du Vieux Chateau, 21</street>
+ <zip>BE-1457</zip>
+ <city>Walhain</city>
+ <website>http://tiny.be</website>
+ </address>
+</addresses>
diff --git a/bin/addons/base/res/partner/report/business_card.xsl b/bin/addons/base/res/partner/report/business_card.xsl
new file mode 100644
index 00000000000..d0d0a5acc4b
--- /dev/null
+++ b/bin/addons/base/res/partner/report/business_card.xsl
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
+
+<xsl:variable name="initial_bottom_pos">20.5</xsl:variable>
+<xsl:variable name="initial_left_pos">0.5</xsl:variable>
+<xsl:variable name="height_increment">5.5</xsl:variable>
+<xsl:variable name="width_increment">8.5</xsl:variable>
+<xsl:variable name="frame_height">5.5cm</xsl:variable>
+<xsl:variable name="frame_width">8.5cm</xsl:variable>
+<xsl:variable name="number_columns">3</xsl:variable>
+<xsl:variable name="max_cards">8</xsl:variable>
+
+<xsl:template match="/">
+ <xsl:apply-templates select="addresses"/>
+</xsl:template>
+
+<xsl:template match="addresses">
+ <document>
+ <template leftMargin="2.0cm" rightMargin="2.0cm" topMargin="2.0cm" bottomMargin="2.0cm" title="Address list" author="Generated by Tiny ERP, Fabien Pinckaers">
+ <pageTemplate id="all">
+ <pageGraphics/>
+ <xsl:apply-templates select="address" mode="frames"/>
+ </pageTemplate>
+ </template>
+ <stylesheet>
+ <paraStyle name="nospace" fontName="Courier" fontSize="12" spaceBefore="0" spaceAfter="0"/>
+ </stylesheet>
+ <story>
+ <xsl:apply-templates select="address" mode="story"/>
+ </story>
+ </document>
+</xsl:template>
+
+<xsl:template match="address" mode="frames">
+ <xsl:if test="position() &lt; $max_frames + 1">
+ <frame>
+ <xsl:attribute name="width">
+ <xsl:value-of select="$frame_width"/>
+ </xsl:attribute>
+ <xsl:attribute name="height">
+ <xsl:value-of select="$frame_height"/>
+ </xsl:attribute>
+ <xsl:attribute name="x1">
+ <xsl:value-of select="$initial_left_pos + ((position()-1) mod $number_columns) * $width_increment"/>
+ <xsl:text>cm</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="y1">
+ <xsl:value-of select="$initial_bottom_pos - floor((position()-1) div $number_columns) * $height_increment"/>
+ <xsl:text>cm</xsl:text>
+ </xsl:attribute>
+ </frame>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="address" mode="story">
+
+ <drawCentredString x="105mm" y="28cm" t="1">PICKINGLIST</drawCentredString>
+ <image x="5mm" y="5m" file="addons/base/report/logo-tiny.png"/>
+<blockTable colWidths="3cm,3cm" style="infos">
+ <tr>
+ <td>Logo</td>
+ <td/>
+ </tr><tr>
+ <td><para><xsl:value-of select="client-id"/></para></td>
+ <td><para><xsl:value-of select="shipping-id"/></para></td>
+ <td><para><xsl:value-of select="picking-date"/></para></td>
+ <td><para><xsl:value-of select="expedition-date"/></para></td>
+ <td><para><xsl:value-of select="command-number"/></para></td>
+ </tr>
+ </blockTable>
+
+ <para style="nospace"><xsl:value-of select="company"/></para>
+ <para style="nospace"><xsl:value-of select="partner_name"/></para>
+ <para style="nospace"><xsl:value-of select="street"/></para>
+ <para style="nospace"><xsl:value-of select="zip"/><xsl:text> </xsl:text><xsl:value-of select="city"/></para>
+ <para style="nospace"><xsl:value-of select="country"/></para>
+ <nextFrame/>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/bin/addons/base/res/partner/report/partner_address.xml b/bin/addons/base/res/partner/report/partner_address.xml
new file mode 100644
index 00000000000..6658639095c
--- /dev/null
+++ b/bin/addons/base/res/partner/report/partner_address.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<addresses>
+ <address type="fields" name="id">
+ <company-title type="field" name="title"/>
+ <company-name type="field" name="name"/>
+ <contact type="zoom" name="address">
+ <type type="field" name="type"/>
+ <title type="field" name="title"/>
+ <name type="field" name="name"/>
+ <street type="field" name="street"/>
+ <street2 type="field" name="street2"/>
+ <zip type="field" name="zip"/>
+ <city type="field" name="city"/>
+ <state type="field" name="state_id.name"/>
+ <country type="field" name="country_id.name"/>
+ </contact>
+ </address>
+</addresses>
diff --git a/bin/addons/base/res/partner/report/partner_address.xsl b/bin/addons/base/res/partner/report/partner_address.xsl
new file mode 100644
index 00000000000..bf8f3b0e76d
--- /dev/null
+++ b/bin/addons/base/res/partner/report/partner_address.xsl
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
+ <xsl:variable name="initial_bottom_pos">20.5</xsl:variable>
+ <xsl:variable name="initial_left_pos">1</xsl:variable>
+ <xsl:variable name="height_increment">6.5</xsl:variable>
+ <xsl:variable name="width_increment">10</xsl:variable>
+ <xsl:variable name="frame_height">5.5cm</xsl:variable>
+ <xsl:variable name="frame_width">10cm</xsl:variable>
+ <xsl:variable name="number_columns">2</xsl:variable>
+ <xsl:variable name="max_frames">8</xsl:variable>
+
+ <xsl:template match="/">
+ <xsl:apply-templates select="addresses"/>
+ </xsl:template>
+
+ <xsl:template match="addresses">
+ <document>
+ <template leftMargin="2.0cm" rightMargin="2.0cm" topMargin="2.0cm" bottomMargin="2.0cm" title="Address list" author="Generated by Tiny ERP">
+ <pageTemplate id="all">
+ <pageGraphics/>
+ <xsl:apply-templates select="address" mode="frames"/>
+ </pageTemplate>
+ </template>
+ <stylesheet>
+ <paraStyle name="nospace" fontName="Courier" fontSize="12" spaceBefore="0" spaceAfter="0"/>
+ </stylesheet>
+ <story>
+ <xsl:apply-templates select="address" mode="story">
+ <xsl:sort select="contact/country"/>
+ <xsl:sort select="contact/zip"/>
+ <xsl:sort select="company-name"/>
+ </xsl:apply-templates>
+ </story>
+ </document>
+ </xsl:template>
+
+ <xsl:template match="address" mode="frames">
+ <xsl:if test="position() &lt; $max_frames + 1">
+ <frame>
+ <xsl:attribute name="width">
+ <xsl:value-of select="$frame_width"/>
+ </xsl:attribute>
+ <xsl:attribute name="height">
+ <xsl:value-of select="$frame_height"/>
+ </xsl:attribute>
+ <xsl:attribute name="x1">
+ <xsl:value-of select="$initial_left_pos + ((position()-1) mod $number_columns) * $width_increment"/>
+ <xsl:text>cm</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="y1">
+ <xsl:value-of select="$initial_bottom_pos - floor((position()-1) div $number_columns) * $height_increment"/>
+ <xsl:text>cm</xsl:text>
+ </xsl:attribute>
+ </frame>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="address" mode="story">
+ <para style="nospace"><xsl:value-of select="company-title"/><xsl:text> </xsl:text><xsl:value-of select="company-name"/></para>
+ <xsl:choose>
+ <xsl:when test="count(contact[type='default']) >= 1">
+ <!-- apply the first 'contact' node with the type 'default' -->
+ <xsl:apply-templates select="contact[type='default'][1]"/>
+ </xsl:when>
+ <xsl:when test="count(contact[type='']) >= 1">
+ <!-- apply the first 'contact' node with an empty type -->
+ <xsl:apply-templates select="contact[type=''][1]"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- apply the first 'contact' node -->
+ <xsl:apply-templates select="contact[1]"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ <nextFrame/>
+ </xsl:template>
+
+ <xsl:template match="contact">
+ <para style="nospace"><xsl:value-of select="title"/><xsl:text> </xsl:text><xsl:value-of select="name"/></para>
+ <para style="nospace"><xsl:value-of select="street"/></para>
+ <para style="nospace"><xsl:value-of select="street2"/></para>
+ <para style="nospace"><xsl:value-of select="zip"/><xsl:text> </xsl:text><xsl:value-of select="city"/></para>
+ <para style="nospace"><xsl:value-of select="state"/></para>
+ <para style="nospace"><xsl:value-of select="country"/></para>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/bin/addons/base/res/partner/wizard/__init__.py b/bin/addons/base/res/partner/wizard/__init__.py
new file mode 100644
index 00000000000..3ba56c79290
--- /dev/null
+++ b/bin/addons/base/res/partner/wizard/__init__.py
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard_sms
+import wizard_spam
+import wizard_clear_ids
+import wizard_ean_check
diff --git a/bin/addons/base/res/partner/wizard/wizard_clear_ids.py b/bin/addons/base/res/partner/wizard/wizard_clear_ids.py
new file mode 100644
index 00000000000..b224ade6ed3
--- /dev/null
+++ b/bin/addons/base/res/partner/wizard/wizard_clear_ids.py
@@ -0,0 +1,45 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import netsvc
+
+class wizard_clear_ids(wizard.interface):
+ def _clear_ids(self, cr, uid, data, context):
+ service = netsvc.LocalService("object_proxy")
+ service.execute(cr.dbname, uid, 'res.partner', 'write', data['ids'], {'ref': False})
+ return {}
+
+ states = {
+ 'init': {
+ 'actions': [_clear_ids],
+ 'result': {'type':'state', 'state':'end'}
+ }
+ }
+wizard_clear_ids('res.partner.clear_ids')
+
diff --git a/bin/addons/base/res/partner/wizard/wizard_ean_check.py b/bin/addons/base/res/partner/wizard/wizard_ean_check.py
new file mode 100644
index 00000000000..e81e40c0bb7
--- /dev/null
+++ b/bin/addons/base/res/partner/wizard/wizard_ean_check.py
@@ -0,0 +1,101 @@
+##############################################################################
+#
+# Copyright (c) 2005-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import math
+from osv import osv
+from tools.misc import UpdateableStr
+import pooler
+
+def _is_pair(x):
+ return not x%2
+
+def _get_ean_key(string):
+ if not string or string=='':
+ return '0'
+ if len(string)!=12:
+ return '0'
+ sum=0
+ for i in range(12):
+ if _is_pair(i):
+ sum+=int(string[i])
+ else:
+ sum+=3*int(string[i])
+ return str(int(math.ceil(sum/10.0)*10-sum))
+
+#FIXME: this is not concurrency safe !!!!
+_check_arch = UpdateableStr()
+_check_fields = {}
+
+def _check_key(self, cr, uid, data, context):
+ partner_table=pooler.get_pool(cr.dbname).get('res.partner')
+ partners = partner_table.browse(cr, uid, data['ids'])
+ _check_arch_lst=['<?xml version="1.0"?>', '<form string="Check EAN13">', '<label string=""/>', '<label string=""/>','<label string="Original" />', '<label string="Computed" />']
+ for partner in partners:
+ if partner['ean13'] and len(partner['ean13'])>11 and len(partner['ean13'])<14:
+ _check_arch_lst.append('<label colspan="2" string="%s" />' % partner['ean13']);
+ key=_get_ean_key(partner['ean13'][:12])
+ _check_arch_lst.append('<label string=""/>')
+ if len(partner['ean13'])==12:
+ _check_arch_lst.append('<label string="" />');
+ else:
+ _check_arch_lst.append('<label string="%s" />' % partner['ean13'][12])
+ _check_arch_lst.append('<label string="%s" />' % key)
+ _check_arch_lst.append('</form>')
+ _check_arch.string = '\n'.join(_check_arch_lst)
+ return {}
+
+def _update_ean(self, cr, uid, data, context):
+ partner_table = pooler.get_pool(cr.dbname).get('res.partner')
+ partners = partner_table.browse(cr, uid, data['ids'])
+ for partner in partners:
+ partner_table.write(cr, uid, data['ids'], {
+ 'ean13': "%s%s" % (partner['ean13'][:12], _get_ean_key(partner['ean13'][:12]))
+ })
+ return {}
+
+class wiz_ean_check(wizard.interface):
+ states = {
+ 'init': {
+ 'actions': [_check_key],
+ 'result': {
+ 'type': 'form',
+ 'arch': _check_arch,
+ 'fields': _check_fields,
+ 'state': (('end', 'Ignore'), ('correct', 'Correct EAN13'))
+ }
+ },
+ 'correct' : {
+ 'actions': [_update_ean],
+ 'result': {
+ 'type': 'state',
+ 'state': 'end'
+ }
+ }
+ }
+
+wiz_ean_check('res.partner.ean13')
diff --git a/bin/addons/base/res/partner/wizard/wizard_sms.py b/bin/addons/base/res/partner/wizard/wizard_sms.py
new file mode 100644
index 00000000000..ff2b10ccebd
--- /dev/null
+++ b/bin/addons/base/res/partner/wizard/wizard_sms.py
@@ -0,0 +1,75 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import netsvc
+
+sms_send_form = '''<?xml version="1.0"?>
+<form string="%s">
+ <separator string="%s" colspan="4"/>
+ <field name="app_id"/>
+ <newline/>
+ <field name="user"/>
+ <field name="password"/>
+ <newline/>
+ <field name="text" colspan="3"/>
+</form>''' % ('SMS - Gateway: clickatell','Bulk SMS send')
+
+sms_send_fields = {
+ 'app_id': {'string':'API ID', 'type':'char', 'required':True},
+ 'user': {'string':'Login', 'type':'char', 'required':True},
+ 'password': {'string':'Password', 'type':'char', 'required':True},
+ 'text': {'string':'SMS Message', 'type':'text', 'required':True}
+}
+
+def _sms_send(self, cr, uid, data, context):
+ service = netsvc.LocalService("object_proxy")
+ res = service.execute(cr.dbname, uid, 'res.partner', 'read', data['ids'], ['gsm'])
+
+ nbr = 0
+ for r in res:
+ to = r['gsm']
+ if to:
+ tools.smssend(data['form']['user'], data['form']['password'], data['form']['app_id'], unicode(data['form']['text'], 'utf-8').encode('latin1'), to)
+ nbr += 1
+ return {'sms_sent': nbr}
+
+class part_sms(wizard.interface):
+ states = {
+ 'init': {
+ 'actions': [],
+ 'result': {'type': 'form', 'arch':sms_send_form, 'fields': sms_send_fields, 'state':[('end','Cancel'), ('send','Send SMS')]}
+ },
+ 'send': {
+ 'actions': [_sms_send],
+ 'result': {'type': 'state', 'state':'end'}
+ }
+ }
+part_sms('res.partner.sms_send')
+
+
diff --git a/bin/addons/base/res/partner/wizard/wizard_spam.py b/bin/addons/base/res/partner/wizard/wizard_spam.py
new file mode 100644
index 00000000000..2ae4307e50f
--- /dev/null
+++ b/bin/addons/base/res/partner/wizard/wizard_spam.py
@@ -0,0 +1,80 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# $Id: wizard_spam.py 1005 2005-07-25 08:41:42Z nicoe $
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import wizard
+import pooler
+import tools
+
+email_send_form = '''<?xml version="1.0"?>
+<form string="Mass Mailing">
+ <field name="from"/>
+ <newline/>
+ <field name="subject"/>
+ <newline/>
+ <field name="text"/>
+</form>'''
+
+email_send_fields = {
+ 'from': {'string':"Sender's email", 'type':'char', 'size':64, 'required':True},
+ 'subject': {'string':'Subject', 'type':'char', 'size':64, 'required':True},
+ 'text': {'string':'Message', 'type':'text', 'required':True}
+}
+
+# this sends an email to ALL the addresses of the selected partners.
+def _mass_mail_send(self, cr, uid, data, context):
+ nbr = 0
+ partners = pooler.get_pool(cr.dbname).get('res.partner').browse(cr, uid, data['ids'], context)
+ for partner in partners:
+ for adr in partner.address:
+ if adr.email:
+ name = adr.name or partner.name
+ to = '%s <%s>' % (name, adr.email)
+#TODO: add some tests to check for invalid email addresses
+#CHECKME: maybe we should use res.partner/email_send
+ tools.email_send(data['form']['from'], [to], data['form']['subject'], data['form']['text'])
+ nbr += 1
+ pooler.get_pool(cr.dbname).get('res.partner.event').create(cr, uid,
+ {'name': 'Email sent through mass mailing',
+ 'partner_id': partner.id,
+ 'description': data['form']['text'], })
+#TODO: log number of message sent
+ return {'email_sent': nbr}
+
+class part_email(wizard.interface):
+ states = {
+ 'init': {
+ 'actions': [],
+ 'result': {'type': 'form', 'arch': email_send_form, 'fields': email_send_fields, 'state':[('end','Cancel'), ('send','Send Email')]}
+ },
+ 'send': {
+ 'actions': [_mass_mail_send],
+ 'result': {'type': 'state', 'state':'end'}
+ }
+ }
+part_email('res.partner.spam_send')
diff --git a/bin/addons/base/res/res_company.py b/bin/addons/base/res/res_company.py
new file mode 100644
index 00000000000..c74458490d0
--- /dev/null
+++ b/bin/addons/base/res/res_company.py
@@ -0,0 +1,105 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+import tools
+
+class res_company(osv.osv):
+ _name = "res.company"
+
+ _columns = {
+ 'name': fields.char('Company Name', size=64, required=True),
+ 'parent_id': fields.many2one('res.company', 'Parent Company', select=True),
+ 'child_ids': fields.one2many('res.company', 'parent_id', 'Childs Company'),
+ 'partner_id': fields.many2one('res.partner', 'Partner', required=True),
+ 'rml_header1': fields.char('Report Header', size=200),
+ 'rml_footer1': fields.char('Report Footer 1', size=200),
+ 'rml_footer2': fields.char('Report Footer 2', size=200),
+ 'currency_id': fields.many2one('res.currency', 'Currency', required=True),
+ }
+
+ def _get_child_ids(self, cr, uid, uid2, context={}):
+ company = self.pool.get('res.users').company_get(cr, uid, uid2)
+ ids = self._get_company_children(cr, uid, company)
+ return ids
+
+ def _get_company_children(self, cr, uid=None, company=None):
+ if not company:
+ return []
+ ids = self.search(cr, uid, [('parent_id','child_of',[company])])
+ return ids
+ _get_company_children = tools.cache()(_get_company_children)
+
+ def _get_partner_hierarchy(self, cr, uid, company_id, context={}):
+ print "Company ID is %s: Looking for parent..." % company_id
+ if company_id:
+ parent_id = self.browse(cr, uid, company_id)['parent_id']
+ if parent_id:
+ return self._get_partner_hierarchy(cr, uid, parent_id.id, context)
+ else:
+ print "No parent: starting descendance search!"
+ return self._get_partner_descendance(cr, uid, company_id, [], context)
+ return []
+
+ def _get_partner_descendance(self, cr, uid, company_id, descendance, context={}):
+ descendance.append(self.browse(cr, uid, company_id).partner_id.id)
+ for child_id in self._get_company_children(cr, uid, company_id):
+ if child_id != company_id:
+ print "Hello, I'm %s, child of %s" % (child_id, company_id)
+ descendance = self._get_partner_descendance(cr, uid, child_id, descendance)
+ return descendance
+
+ def __init__(self, *args, **argv):
+ return super(res_company, self).__init__(*args, **argv)
+
+ #
+ # This function restart the cache on the _get_company_children method
+ #
+ def cache_restart(self, uid=None):
+ self._get_company_children()
+
+ def create(self, *args, **argv):
+ self.cache_restart()
+ return super(res_company, self).create(*args, **argv)
+
+ def write(self, *args, **argv):
+ self.cache_restart()
+ return super(res_company, self).write(*args, **argv)
+
+ def _get_euro(self, cr, uid, context={}):
+ try:
+ return self.pool.get('res.currency').search(cr, uid, [('rate', '=', 1.0),])[0]
+ except:
+ return 1
+
+ _defaults = {
+ 'currency_id': _get_euro,
+ }
+
+res_company()
+
diff --git a/bin/addons/base/res/res_currency.py b/bin/addons/base/res/res_currency.py
new file mode 100644
index 00000000000..b193144fafc
--- /dev/null
+++ b/bin/addons/base/res/res_currency.py
@@ -0,0 +1,70 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# $Id: account.py 1005 2005-07-25 08:41:42Z nicoe $
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+import time
+import netsvc
+from osv import fields, osv
+import ir
+
+from tools.misc import currency
+
+#from _common import rounding
+
+import mx.DateTime
+from mx.DateTime import RelativeDateTime, now, DateTime, localtime
+
+class res_currency(osv.osv):
+ _name = "res.currency"
+ _description = "Currency"
+ _columns = {
+ 'name': fields.char('Currency', size=32, required=True),
+ 'code': fields.char('Code', size=3),
+ 'date': fields.date('Last rate update', readonly=True),
+ 'rate': fields.float('Relative Change rate',digits=(12,6)),
+ #'digits': fields.integer('Displayed Digits'),
+ 'accuracy': fields.integer('Computational Accuracy'),
+ 'rounding': fields.float('Rounding factor'),
+ 'active': fields.boolean('Active'),
+ }
+ _defaults = {
+ 'date': lambda *a: time.strftime('%Y-%m-%d'),
+ 'active': lambda *a: 1,
+ }
+ _order = "code"
+
+ def compute(self, cr, uid, from_currency_id, to_currency_id, from_amount):
+ if to_currency_id==from_currency_id:
+ return from_amount
+ [from_currency]=self.read(cr, uid, [from_currency_id])
+ [to_currency] = self.read(cr, uid, [to_currency_id])
+ return currency(from_amount * from_currency['rate']/to_currency['rate'], to_currency['accuracy'], to_currency['rounding'])
+
+
+res_currency()
+
diff --git a/bin/addons/base/res/res_currency_view.xml b/bin/addons/base/res/res_currency_view.xml
new file mode 100644
index 00000000000..314ef905670
--- /dev/null
+++ b/bin/addons/base/res/res_currency_view.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<terp>
+<data>
+
+ #---------------------------------------------------------
+ # Currencies
+ #---------------------------------------------------------
+ <record model="ir.ui.view" id="view_currency_tree">
+ <field name="name">res.currency.tree</field>
+ <field name="model">res.currency</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Currencies">
+ <field name="name"/>
+ <field name="code"/>
+ <field name="rate"/>
+ <field name="date"/>
+ <field name="rounding"/>
+ <field name="accuracy"/>
+ <field name="active" />
+ </tree>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_currency_form">
+ <field name="name">res.currency.form</field>
+ <field name="model">res.currency</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Currency">
+ <field name="name" select="1" colspan="3"/>
+ <field name="code" select="1"/>
+ <field name="active" select="1"/>
+ <field name="rate"/>
+ <field name="date" select="1"/>
+ <field name="rounding"/>
+ <field name="accuracy"/>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="action_currency_form">
+ <field name="name">res.currency</field>
+ <field name="res_model">res.currency</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem name="Partners/Configuration/Localisation/Currencies" action="action_currency_form" sequence="11"/>
+
+ <menuitem name="Administration/Configuration/Base/Define Currencies" action="action_currency_form" sequence="1" id="menu_currency_def"/>
+
+</data>
+</terp>
+
diff --git a/bin/addons/base/res/res_request.py b/bin/addons/base/res/res_request.py
new file mode 100644
index 00000000000..8255df8bf68
--- /dev/null
+++ b/bin/addons/base/res/res_request.py
@@ -0,0 +1,124 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import osv, fields
+import time
+
+def _links_get(self, cr, uid, context={}):
+ obj = self.pool.get('res.request.link')
+ ids = obj.search(cr, uid, [])
+ res = obj.read(cr, uid, ids, ['object', 'name'], context)
+ return [(r['object'], r['name']) for r in res]
+
+class res_request(osv.osv):
+ _name = 'res.request'
+
+ def request_send(self, cr, uid, ids, *args):
+ for id in ids:
+ cr.execute('update res_request set state=%s,date_sent=%s where id=%d', ('waiting', time.strftime('%Y-%m-%d %H:%M:%S'), id))
+ cr.execute('select act_from,act_to,body,date_sent from res_request where id=%d', (id,))
+ values = cr.dictfetchone()
+ # this will be truncated automatically at creation
+ values['name'] = values['body'] or '/'
+ values['req_id'] = id
+ self.pool.get('res.request.history').create(cr, uid, values)
+ return True
+
+ def request_reply(self, cr, uid, ids, *args):
+ for id in ids:
+ cr.execute("update res_request set state='active', act_from=%d, act_to=act_from, trigger_date=NULL, body='' where id=%d", (uid,id))
+ return True
+
+ def request_close(self, cr, uid, ids, *args):
+ self.write(cr, uid, ids, {'state':'closed', 'active':False})
+ return True
+
+ def request_get(self, cr, uid):
+ cr.execute('select id from res_request where act_to=%d and (trigger_date<=%s or trigger_date is null) and active=True', (uid,time.strftime('%Y-%m-%d')))
+ ids = map(lambda x:x[0], cr.fetchall())
+ cr.execute('select id from res_request where act_from=%d and (act_to<>%d) and (trigger_date<=%s or trigger_date is null) and active=True', (uid,uid,time.strftime('%Y-%m-%d')))
+ ids2 = map(lambda x:x[0], cr.fetchall())
+ return (ids, ids2)
+
+ _columns = {
+ 'create_date': fields.datetime('Created date', readonly=True),
+ 'name': fields.char('Subject', states={'waiting':[('readonly',True)],'active':[('readonly',True)],'closed':[('readonly',True)]}, required=True, size=128),
+ 'active': fields.boolean('Active'),
+ 'priority': fields.selection([('0','Low'),('1','Normal'),('2','High')], 'Priority', states={'waiting':[('readonly',True)],'closed':[('readonly',True)]}, required=True),
+ 'act_from': fields.many2one('res.users', 'From', required=True, readonly=True, states={'closed':[('readonly',True)]}),
+ 'act_to': fields.many2one('res.users', 'To', required=True, states={'waiting':[('readonly',True)],'closed':[('readonly',True)]}),
+ 'body': fields.text('Request', states={'waiting':[('readonly',True)],'closed':[('readonly',True)]}),
+ 'date_sent': fields.datetime('Date', readonly=True),
+ 'trigger_date': fields.datetime('Trigger Date', states={'waiting':[('readonly',True)],'closed':[('readonly',True)]}),
+ 'ref_partner_id':fields.many2one('res.partner', 'Partner Ref.', states={'closed':[('readonly',True)]}),
+ 'ref_doc1':fields.reference('Document Ref 1', selection=_links_get, size=128, states={'closed':[('readonly',True)]}),
+ 'ref_doc2':fields.reference('Document Ref 2', selection=_links_get, size=128, states={'closed':[('readonly',True)]}),
+ 'state': fields.selection([('draft','draft'),('waiting','waiting'),('active','active'),('closed','closed')], 'State', required=True, readonly=True),
+ 'history': fields.one2many('res.request.history','req_id', 'History')
+ }
+ _defaults = {
+ 'act_from': lambda obj,cr,uid,context={}: uid,
+ 'act_to': lambda obj,cr,uid,context={}: uid,
+ 'state': lambda obj,cr,uid,context={}: 'draft',
+ 'active': lambda obj,cr,uid,context={}: True,
+ 'priority': lambda obj,cr,uid,context={}: '1',
+ }
+ _order = 'priority desc, trigger_date, create_date desc'
+ _table = 'res_request'
+res_request()
+
+class res_request_link(osv.osv):
+ _name = 'res.request.link'
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True, translate=True),
+ 'object': fields.char('Object', size=64, required=True),
+ 'priority': fields.integer('Priority'),
+ }
+ _defaults = {
+ 'priority': lambda *a: 5,
+ }
+ _order = 'priority'
+res_request_link()
+
+class res_request_history(osv.osv):
+ _name = 'res.request.history'
+ _columns = {
+ 'name': fields.char('Summary', size=128, states={'active':[('readonly',True)],'waiting':[('readonly',True)]}, required=True),
+ 'req_id': fields.many2one('res.request', 'Request', required=True, ondelete='cascade', select=True),
+ 'act_from': fields.many2one('res.users', 'From', required=True, readonly=True),
+ 'act_to': fields.many2one('res.users', 'To', required=True, states={'waiting':[('readonly',True)]}),
+ 'body': fields.text('Body', states={'waiting':[('readonly',True)]}),
+ 'date_sent': fields.datetime('Date sent', states={'waiting':[('readonly',True)]}, required=True)
+ }
+ _defaults = {
+ 'name': lambda *a: 'NoName',
+ 'act_from': lambda obj,cr,uid,context={}: uid,
+ 'act_to': lambda obj,cr,uid,context={}: uid,
+ 'date_sent': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+ }
+res_request_history()
+
diff --git a/bin/addons/base/res/res_request_view.xml b/bin/addons/base/res/res_request_view.xml
new file mode 100644
index 00000000000..5d332ec7f0b
--- /dev/null
+++ b/bin/addons/base/res/res_request_view.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0"?>
+<terp>
+<data>
+ <record model="ir.ui.view" id="res_request_tree-view">
+ <field name="name">res.request.tree</field>
+ <field name="model">res.request</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Requests">
+ <field name="create_date" />
+ <field name="act_from"/>
+ <field name="act_to"/>
+ <field name="name"/>
+<!--
+ <field name="act_title"/>
+-->
+ <field name="priority"/>
+ <field name="trigger_date"/>
+<!--
+ <field name="ref_partner_id"/>
+-->
+ <field name="state"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="res_request-view">
+ <field name="name">res.request.form</field>
+ <field name="model">res.request</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Requests">
+ <notebook>
+ <page string="Request">
+ <separator string="Description" colspan="4"/>
+ <field name="act_from" select="1"/>
+ <field name="act_to" select="1"/>
+ <field name="name" colspan="3" select="1"/>
+ <newline/>
+ <field name="body" colspan="4" select="1"/>
+ <field name="trigger_date" select="1"/>
+ <group col="2" colspan="2">
+ <button name="request_send" states="draft,active" string="Send" type="object"/>
+ <button name="request_reply" states="waiting" string="Reply" type="object"/>
+ </group>
+
+ <separator string="References" colspan="4"/>
+ <field name="ref_partner_id"/>
+ <field name="priority" select="1"/>
+ <field name="ref_doc1" colspan="3"/>
+ <field name="ref_doc2" colspan="3"/>
+ <separator string="Status" colspan="4"/>
+ <field name="state"/>
+ <group col="3" colspan="2">
+ <field name="active" select="1"/>
+ <button name="request_close" states="waiting,draft,active" string="End of Request" type="object"/>
+ </group>
+ </page><page string="History">
+ <field name="history" nolabel="1" colspan="4" widget="one2many_list"/>
+ </page>
+ </notebook>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="res_request-act">
+ <field name="name">res.request.form</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.request</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="res_request-view"/>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Requests/All Requests" action="res_request-act"/>
+
+ <record model="ir.ui.view" id="res_request_link-view">
+ <field name="name">res.request.link.form</field>
+ <field name="model">res.request.link</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Request Link">
+ <field name="name" select="1"/>
+ <field name="object" select="1"/>
+ <field name="priority"/>
+ </form>
+ </field>
+ </record>
+
+ <record model="ir.actions.act_window" id="res_request_link-act">
+ <field name="name">res.request.link.form</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">res.request.link</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="res_request_link-view"/>
+ </record>
+ <menuitem name="Administration/Custom/Low level/Base/Requests/Request Links" action="res_request_link-act"/>
+
+
+ <record model="ir.ui.view" id="res_request_history_tree-view">
+ <field name="name">res.request.history.tree</field>
+ <field name="model">res.request.history</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Request History">
+ <field name="act_from"/>
+ <field name="act_to"/>
+ <field name="date_sent"/>
+ <field name="name"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="res_request_history_form-view">
+ <field name="name">res.request.history.form</field>
+ <field name="model">res.request.history</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Request History">
+ <field name="act_from"/>
+ <field name="act_to"/>
+ <field name="date_sent"/>
+ <newline/>
+ <field name="body" colspan="3"/>
+ </form>
+ </field>
+ </record>
+</data>
+</terp>
diff --git a/bin/addons/base/res/res_user.py b/bin/addons/base/res/res_user.py
new file mode 100644
index 00000000000..d05174203ae
--- /dev/null
+++ b/bin/addons/base/res/res_user.py
@@ -0,0 +1,122 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from osv import fields,osv
+import tools
+
+class groups(osv.osv):
+ _name = "res.groups"
+ _columns = {
+ 'name': fields.char('Group Name', size=64, required=True),
+ }
+groups()
+
+
+class roles(osv.osv):
+ _name = "res.roles"
+ _columns = {
+ 'name': fields.char('Role Name', size=64, required=True),
+ 'parent_id': fields.many2one('res.roles', 'Parent', select=True),
+ 'child_id': fields.one2many('res.roles', 'parent_id', 'Childs')
+ }
+ _defaults = {
+ }
+ def check(self, cr, uid, ids, role_id):
+ if role_id in ids:
+ return True
+ cr.execute('select parent_id from res_roles where id=%d', (role_id,))
+ roles = cr.fetchone()[0]
+ if roles:
+ return self.check(cr, uid, ids, roles[0])
+ return False
+roles()
+
+class lang(osv.osv):
+ _name = "res.lang"
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True),
+ 'code': fields.char('Code', size=5, required=True),
+ 'translatable': fields.boolean('Translatable'),
+ 'active': fields.boolean('Active'),
+ }
+ _defaults = {
+ 'active': lambda *a: 1,
+ 'translatable': lambda *a: 0,
+ }
+lang()
+
+class users(osv.osv):
+ _name = "res.users"
+ _log_access = False
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True, select=True),
+ 'login': fields.char('Login', size=64, required=True),
+ 'password': fields.char('Password', size=64, invisible=True),
+ 'signature': fields.text('Signature', size=64),
+ 'address_id': fields.many2one('res.partner.address', 'Address'),
+ 'active': fields.boolean('Active'),
+ 'action_id': fields.many2one('ir.actions.actions', 'Action'),
+ 'groups_id': fields.many2many('res.groups', 'res_groups_users_rel', 'uid', 'gid', 'groups'),
+ 'roles_id': fields.many2many('res.roles', 'res_roles_users_rel', 'uid', 'rid', 'Roles'),
+ 'company_id': fields.many2one('res.company', 'Company'),
+ }
+ _sql_constraints = [
+ ('login_key', 'UNIQUE (login)', 'You can not have two users with the same login !')
+ ]
+ _defaults = {
+ 'password' : lambda obj,cr,uid,context={} : '',
+ 'active' : lambda obj,cr,uid,context={} : True,
+ }
+ def company_get(self, cr, uid, uid2):
+ company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.id
+ return company_id
+ company_get = tools.cache()(company_get)
+
+ def write(self, cr, uid, *args, **argv):
+ res = super(users, self).write(cr, uid, *args, **argv)
+ # Restart the cache on the company_get method
+ self.company_get()
+ return res
+
+ def unlink(self, cr, uid, ids):
+ if 1 in ids:
+ raise osv.except_osv('Can not remove root user !', 'You can not remove the root user as it is used internally for resources created by Tiny ERP (updates, module installation, ...)')
+ return super(users, self).unlink(cr, uid, ids)
+
+ def name_search(self, cr, user, name, args=[], operator='ilike', context={}):
+ ids = self.search(cr, user, [('login','=',name)]+ args)
+ if not ids:
+ ids = self.search(cr, user, [('name',operator,name)]+ args)
+ return self.name_get(cr, user, ids)
+
+ def copy(self, cr, uid, id, default=None, context={}):
+ login = self.read(cr, uid, [id], ['login'])[0]['login']
+ default.update({'login': login+' (copy)'})
+ return super(users, self).copy(cr, uid, id, default, context)
+users()
+
diff --git a/bin/addons/gen_graph.sh b/bin/addons/gen_graph.sh
new file mode 100755
index 00000000000..56e43569dff
--- /dev/null
+++ b/bin/addons/gen_graph.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+./module_graph.py | dot -Tpng -o > module_graph.png
diff --git a/bin/addons/module_graph.py b/bin/addons/module_graph.py
new file mode 100644
index 00000000000..64f79dd8f1a
--- /dev/null
+++ b/bin/addons/module_graph.py
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+
+import os
+
+print 'digraph G {'
+for f in os.listdir('.'):
+ if os.path.isfile(os.path.join(f,"__terp__.py")):
+ info=eval(file(os.path.join(f,"__terp__.py")).read())
+ if info.get('active', True):
+ for name in info['depends']:
+ print '\t%s -> %s;' % (f, name)
+print '}'