diff options
| author | pinky <> | 2006-12-07 13:41:40 +0000 |
|---|---|---|
| committer | pinky <> | 2006-12-07 13:41:40 +0000 |
| commit | 004a0b996ff8f269451e07346f71a129a1f3fbaf (patch) | |
| tree | 48f43c1fc2ca4c1f4d5648e7e73553d3f4ccae06 /bin/addons | |
New trunk
bzr revid: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
Diffstat (limited to 'bin/addons')
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 & 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 & 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 & 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','<>','uninstalled'),('state','<>','installed'),('state','<>','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 Binary files differnew file mode 100644 index 00000000000..b97959a8d0a --- /dev/null +++ b/bin/addons/base/module/report/ir_module_reference.sxw 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)>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() < $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() < $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 '}' |
