You are on page 1of 186

1

1. Installation
1. Dependencies
2. Installation Steps
3. Upgrading Versions
4. Config files
1. Server's config file: ~/.terp_serverrc
2. Client's config file: ~/.terprc
5. Command line options
6. Shutting down the server
2. Translations
1. Translation in Reports
3. The client
4. Parameter settings of Modules
1. ModulesAdminBase
1. Management of access rights
2. The actions
3. Sequences
4. Requests
5. Customizing the menu
2. ModulesAdminCRM
3. ModulesAdminAccount
1. ModulesAdminAccountChart
2. ModulesAdminAccountCurrency
3. ModulesAdminAccountTax
4. ModulesAdminAccountImputations
4. ModulesAdminStock
5. ModulesAdminSale
6. Subscriptions
7. Audittrail
8. Configuring Projects
5. Architecture
1. Program structure
2. Objects
3. Views
4. Workflows
5. Reports
6. Services
6. Objects
1. Introduction
2. Definition
1. Fields
2. Default values
3. Constraints
4. Methods
5. Example
6. Inheritance
3. Access
1. Access types
2. Predefined Methods
3. Object Specific Methods
4. ObjectsWritingValues
4. Property Fields
7. Data loading
1. Introduction
2. Files loading

05/02/08
2
3. XML files structure
1. The data
2. IR Use
4. CSV Files
5. XML data files convention
6. Managing updates
8. Views
1. Introduction
2. File Format
3. Special Properties
4. Inheritancy in Views
5. Graphs in forms
9. Workflow
1. Introduction
2. Example of Workflows
1. Discount on orders
2. Purchase order and Invoice
3. Terminology
4. Define a Workflow
1. Workflow
2. The nodes (activities)
3. Transitions
5. Expressions
6. The notion of Roles
7. Error handling
10. Reports
1. Introduction
2. XSL:RML reports
1. XML Template
2. Introduction to RML
3. XSL:RML Stylesheet
3. Each reports with its own corporate header
4. Bar Codes
5. OpenOffice Report
6. Usual TAGS
7. How to add a new report
8. Customize report Header
11. Wizards
1. Introduction
2. Principles
3. How to add a new wizard
12. Events
1. Linking client events to actions
13. Information Repository (deprecated)
1. Introduction
2. Structure
3. Access Methods
14. MRPModule
1. Workcenters
2. Bill of Materials
3. Production Order
15. Modules
1. Writing a new module
2. Modules guidelines
3. Network management

05/02/08
3

16. Webservices
1. WebservicesIntro
2. WebservicesXMLRPC
3. An example in PHP
4. An example in Python
17. Misc
1. Load Balancing between servers
18. Multi − Company
19. Internationalization

05/02/08
4

1 Installation

05/02/08
5

1.1 Dependencies
1.1.1 The server
The Tiny ERP server depends on the following packages:

• python >= 2.3 : http://python.org


• python2.3−xml
• libxml2−python2.3
• libxslt1−python2.3
• PsycoPg : http://initd.org/projects/psycopg1
• Python Imaging Library : http://www.pythonware.com/products/pil/ (only if you use bitmap in reports)
• Reportlab : http://reportlab.org
• PostgreSQL: http://postgresql.org

Optional, if you plan to render workflows:

• PyParsing : http://pyparsing.sourceforge.net/
• PyDot : http://dkbza.org/pydot.html
• graphviz: http://www.graphviz.org/

Have a look in the user manual for installation instructions.

1.1.2 The client


The client depends on the following packages:

• python >= 2.3 : http://python.org


• GTK >= 2.6 : http://gtk.org

Optional, for Windows users:

• Win32Com : http://www.python.org/windows/win32com/

05/02/08
6

1.2 Installation Steps


First check that all the required dependencies are installed.

Then create the terp database. You have to make sure that your user has the correct credentials to create databases
with postgres, for more information on this subject please refer to the PostgreSQL manual.

$ createddb terp −−encoding=unicode

Once the database created, you can start Tiny ERP. The content of the data base will automatically be created at the
first start.

$ ./tinyerp−server

05/02/08
7

1.3 Upgrading Versions


The upgrade of version is automatic and don't need any special script. In fact, the server is able to rebuild
automatically the data base and the data from a disturbed version.

The tables are rebuilt from the actual data base in production. To rebuild these tables, the server uses the description
of the objets and adds, modify or delete new fields.

To ask the data base upgrade while having a new version installed, you need to do:

−> terp_server.py −−update=all

You can also upgrade one or several modules:

−> terp_server.py −−update=account,base

The data base are rebuilt because of the XML files. For more information on these functionalities, go to the section
concerning XML files.

You can also execute the server with −−init=all. The server rebuilds then the data of beginning and deletes all existing
datas to come back in his basic configuration.

05/02/08
8

1.4 Config files


Two configuration files are available:

• one for the client: ~/.terprc


• one for the server: ~/.terp_serverrc

Those files follow the convention used by python's ConfigParser module .

Those files are not necessary. If they are not found, the programs will start with the default configuration.

The client configuration file is automatically generated upon the first start. That of the server can be automatically
created with the command:

tinyerp_server.py −s

05/02/08
9

1.4.1 Server's config file: ~/.terp_serverrc


The server configuration file @.terp_serverrc@ is used to save server statup options. Here is the list of the available
options:

interface
Address the server will be bound to
port
Port the server will listen on
database
Name of the database to use
user
Username used when connecting to the database
translate_in
File used to translate tinyerp to your language
translate_out
File used to export the language tinyerp use
language
Use this languages as the server's language. This must be specified as the language two letter code.
verbose
Will used debugged output

This file is written on the disk when the option "−s" ou "−−save" has been put in parameter at the first start of the
server.

Here is the configuration by default for the server

[options]
verbose = False
xmlrpc = True
database = terp
update = {}
port = 8069
init = {}
interface = 127.0.0.1
reportgz = False

05/02/08
10

1.4.2 Client's config file: ~/.terprc


Here is the list of the available options:

login section
login
login name to use to connect to TinyERP server
server
address used by the server
port
port used by the server

path section
share
path used to find tinyERP's file
pixmaps
path used to fin tinyERP's pixmaps file

tip section
autostart
Should the client display tips at startup
position
Tip number the client will display

form section
autosave
Will the client automatically save the change you made to a record

printer section
preview
Preview report before printing
softpath
Path to the pdf previewer
softpath_html
Path to the html previewer
path
Command used to print

logging section
logger
log channels to display. List values are: @common@, @common.message@, @view@, @view.form@,
@common.options@, @rpc.request@, @rpc.result@, @rpc.exception@
level
logging level to show
output

05/02/08
11

file used by the logger


verbose
set the log level to INFO

client section
default_path
Default path used by the client when saving/loading datas.

Default values
[login]
login = admin
port = 8069
server = 192.168.1.4

[printer]
path = none
preview = True
softpath = none

[logging]
output = stdout
logger =
verbose = True
level = ERROR

[form]
autosave = False

[client]
default_path = /home/ged

05/02/08
12

1.5 Command line options


The following options can be passed into the server with the line:

−h, −−help
Show this help screen and leave the program.
−−version
Show the number of the version and leave.
−cFILE, −−config= FILE
Use an alternative configuration file. By default, the server use the file ~/.terp_serverrc
−s, −−save
Save the server configurationin the file ~/.terp_serverrc. By default, the options given to the line order are not
saved.
−pPORT, −−port= PORT
Specify the TCP port that the server must use. By default, the server uses the port 8069.
−−interface= INTERFACE
Specify the network interface that the server must use. In most of the case, you have to use the IP address of
the computer on which is the server. By default, the server uses the interface localhost and can accept the
connections only from the clients who are on the same computer.
−dDATABASE, −−database= DATABASE
Specify the name of the data base.
−iMODULE, −−init= MODULE
Initialize a module (use "all" for all modules). Attention: this option will delete all datas linked to this
module!
−uMODULE, −−update= MODULE
Upgrade a module (use "all" for all modules).
−v, −−verbose
Active the verbose mode.

Relatives langages options


These options allows you to use Tiny ERP through other langage (than english). Go to the section Traductions for
more details on the system of translations.

−lLANGUAGE, −−language= LANGUAGE


Specify the langage of the translation file. Use this option in combination with the option −−i18n−export
or
−−i18n−import .
−−i18n−export=
FILEPATH
Export all the sentences to translate in the file FILEPATH. The option '−l' must also be used.
−−i18n−import=
FILEPATH
Import the file CSV FILEPATH. This file must contain all the translations for the choosen langage by the
parameter −l.

Don't forget to select in the client preferences the choosen langage.

05/02/08
13

1.6 Shutting down the server


The easiest way the shut down the server is to send the SIG INT signal to the server.

$ kill −2 pid

where pid is the processus ID of the server.

05/02/08
14
2 Translations
Tiny ERP is multi−langage. You can add as much as langages you want. Each user can work with the interface in his
langage. Moreover, some ressources (text of reports, name of the products, ...) may also be translated.

This section explain how to change the langage of the program for the users and how to add new langages to Tiny
ERP.

First of all, you have to know that all the labels of interface is stored in the server. In the same way, the translations
are also stored in the server. By default, the english dictionnary is charged in the server, so if the users want to try
Tiny ERP in other langage, you have to charge these langages (in the server). The method for this is explain in the
next paragraph "To charge a translation file on the server".

But it is not possible to store "everything" on the server. Indeed, the user gots some menus, buttons, etc... that must
contain some text even before being connected to a server. These few words and sentences are translated using
GETTEXT. The choosen langage by default for these ones is the langage of the computer in which the user is
subscribe.

As I said before, the system of translation in Tiny ERP isn't limited to interface texts: it works also for reports and the
"content" of some fields (of data base). Obviously, all the data base fileds must not be translated. The fields where the
content is multi−langage are marked by a flag:

How to change the langage of the interface user ?


The langage is an user preference. To change the langage of the current user, you click on the menu: User >
Preferences.

An administrator may also modify the preferences of the user (including the langage of the interface) in the menu:
Administration > Users > Users. He has to choose an user and notch on "préférences".

Charge a file of translation on the server


To import a file having translations, use the next order:

./tinyerp_server.py −−i18n−import=file.csv −lLANG

Note that the translation file must be encoded in UTF8!

05/02/08
15

2.1 Translation Reports


TO get printscreen report in prefered language following methods are used.

fields_view_get(cr, uid, view_type='tree', context=context)

read(cr, uid, datas['ids'], result['fields'].keys(), context )

fields_view_get

Description: This method is use for getting all fields from the current view.

Signature
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context={})

Parameters
* context is a dictionary that contains: {'lang':'prefered language'}

read
Description
List of fields ressources values.
Signature

def read(self, cr, uid, ids, fields=None, context={})

Parameters

* ids: the list ressources identifiers to read.


* fields (optionnal): the list of the interested fields. If we don't introduce
something for this parameter, the function check all the fields
* context (optional): the actual context.

Returns

A list of dictionaries (a dictionary per resource asked) of the form


[{'name_of_the_field': value, ...}, ...]

all methods on objects (fields_view_get, read) process context before sending the result using values in the
ir.translation table.

05/02/08
16

To translate a new langage


To translate or modify the translation of a langage already translated, you have to:

1. Export all the sentences to translate in a file CSV


To export this file, use the order:

./tinyerp_server.py −−i18n−export=file.csv −lLANG

or LANG is the langage in which you want to translate the program.

2. to translate the last column of the file


You can make a translaion for a langaga which has already been translated or for a new one. If you ask for a langage
already translated, the sentences already translated will be written in the last column.

For example, here are the first lines of a translation file (dutch):

type name res_id src value


field "account.account,code" 0 Code Code
field "account.account,name" 0 Name Name
model "account.account,name" 2 Assets Aktiva
model "account.account,name" 25 Results Salden
model "account.account,name" 61 Liabilities Verbindlichkeiten

3. Import this file to Tiny ERP (as explained at the precedent


section)

Notes
• It is advised to start these orders when the data base is empty because you could publish directly the file of
translation.

To create a new data base (named 'terp_test'), use the next orders:

createdb terp_test −−encoding=unicode


terp_server.py −−database=terp_test −−init=all

Alternatively, you could also delete your current data base using this order:

dropdb terp
createdb terp −−encoding=unicode
terp_server.py −−init=all

• If you translate the program in a new lanage or modify a current translation, send the translation file to
fp@tiny.be attached by a file credits.txt. We will publish this translation on our website (http://tinyerp.org).
• It is possible to translate only a part of Tiny ERP, to continu a translation already begun, or even to correct
few sentences of a translation file.

05/02/08
17

3 The client
The type of message
Differents types of messages can be analysed:

• rpc
♦ exception
♦ request
♦ result
• common
♦ message
♦ option
• view
♦ form
♦ tree

If you want to analyse several types of message, you can divide them with a comma in the arguments of the user;

tinyerp.py −drpc.result,rpc.request −lDEBUG

This request can post the XML−RPC tasks sent to the server and the results given back.

Levels of logs
Some levels are available. You can, for example, log only the messages that are coming from an alert or an error.

The available levels are:

• DEBUG
• INFO
• WARNING
• ERROR
• CRITICAL

The user argument concern the minimum level to log. So, if you select DEBUG, you obtain all the messages. If you
select WARNING, you obtain the messages from the following levels; WARNING, ERROR, CRITICAL.

File of configuration; Options


The options are:

logging.logger
list of the namespace to log separated by a comma. Value by default: none.
logging.level
level of log. Value by default: WARNING

Example (~/.terprc):

[logging]
output = stdout
logger = rpc.request,common.message
verbose = False

05/02/08
18
level = INFO

We can also use the next parameters to the order:

• −l level ou −−log−level=level
• −d channels ou −−log=channels

Go to tinyerp.py −−help for more details...

Option verbose
The option verbose allows you to log at the level 'INFO'. this option is the same as the option −−log−level=INFO

In the file of onfiguration:

logging.verbose: True

Also available as parameter at the line:

−v, −−verbose

Examples
Requests XML−RPC
To log the requests XML−RPC but not the responses:

./terp.py −−log=rpc.request −−log−level=debug

To log all events XML−RPC (requests, responses, exceptions):

./terp.py −−log=rpc −−log−level=debug

To log the requests and the exceptions but not the responses:

./terp.py −−log=rpc.request,rpc.exception −−log−level=info

05/02/08
19

4 Parameter settings of Modules


The differents modules of Tiny ERP can be installed or not according to the needs. All the modules are in the
repertoty: '/addons' of the server. You have to copy/delete the repertories to install or delete the Tiny ERP modules.

Some modules depend on other modules. See the file addons/module/__terp__.py for more information on the
dependences.

Here is an example of __terp__.py:

{
»···"name" : "Tiny TERP Accounting",
»···"version" : "1.0",
»···"depends" : ["base"],
»···"init_xml" : [ "account_workflow.xml", "account_data.xml", "account_demo.xml"],
»···"update_xml" : [ "account_view.xml", "account_report.xml", "account_wizard.xml"],
}

When initializing a module, the files in the init_xml list are evaluated in turn the the files in update_xml are evaluated.
When updating a module only the files from update_xml are evaluated.

05/02/08
20
4.1 ModulesAdminBase
This module is the only one who needs to be installed in Tiny ERP (mandatory).

05/02/08
21

4.1.1 Management of access rights


Three concepts are differentiated into Tiny ERP;

1. The users: person identified by his login/password


2. The groups: define the access rights of the ressources
3. The roles: determine the roles/duties of the users

The users
They represent physical persons. These are identified with a login and a password. A user may belong to severel
groups and may have several roles.

A user must have an action. This action is executed when the user connects to the program with his login and
password. An exampleof action would; open the menu at 'Operations'.

The preferences of the user are available with the preference icon. You can, for example, through these preferences,
determine the langage of work of this user. By default, English is set up.

A user can modify his own preferences while he's working on Tiny ERP. For that, he clicks on this menu; User >
Preference.

The groups
The groups determine the access rights to the different ressources. There is three types of right:

• The reading access: reading of a file,


• The writing access: recording & creation,
• The execution access: the buttons of workflows or wizards.

A user can belong to several groups. If he belongs to some groups, we use always the group with the highest rights for
a selected ressource.

The roles
The roles define a hierarchical structure in tree. They represent the different posts of the company. The bigest role has
automatically the rights of all its wires. Example:

• CEO

05/02/08
22

♦ Technical manager
◊ Chief of the project
⋅ Developpers
⋅ Testers
♦ Commercial manager
◊ Salers
◊ ...

If we want to valid the test of a program (=role Testers), it may be done by a user having one of the following roles:
Testers, Chief of the project, Technical manager, CEO.

The roles are used for the transitions of Workflow actions in the confirmation actions, choice or validation. Their
implication will be detailed in the Workflow section.

05/02/08
23

4.1.2 The actions


The actions define the reactions of a user in front of some events; login of a new user, double−click on an invoice,
click on the action button, ...

It exists different type of simple actions:

• Window: opening of a new window


• Report: The prints of a report
♦ Custom Report: The personalized reports
♦ RML Report: The XSL:RML reports
• Wizard: The startings of a Wizard
• Execute: The execution of a method on the server side
• Group: Gather some actions in one group

The actions are used for the next events;

• User connection
• The user double−click on the menu
• The user click on the icon 'print' or 'action'

Example of events
Into Tiny ERP, all the actions are described and not configured. Two examples are briefly describe here:

• Opening of a window when double−click in the menu


• User connection

Opening of the menu

When the user open the option of the menu "Operations > Partners > Partners Contact"., the next steps are done to
give to the user information on the action to undertake.

1. Search the action in the IR.


2. Execution of the action
1. If the action is the type Opening the Window; it indicates to the user that a new window must be
opened for a selected object and it gives you the view (form or list) and the filed to use (only the
pro−forma invoice).
2. The user asks the object and receives information necessary to trace a form; the fields description and
the XML view.

User connection

When a new user is connected to the sever, the client must search the action to use for the first screen of this user.
Generally, this action is; open the menu in the 'Operations' section.

The steps are:

1. Reading of a user file to obtain ACTION_ID


2. Reading of the action and execution of this one

05/02/08
24

The 'Window' actions


They indicate at the user that he has to open a new window in a new 'onglet'.

The fields
Action Name
The action name
Action Type
Always 'ir.actions.act_window'
View Ref
The view used for showing the object
Model
The model of the objet to post
Type of View
The type of view (Tree/Form)
Domain Value
The domaine that decreases the visible datas with this view

The view
The view describes how is traced the edition form or the datas tree/list. The views are from type 'Form' or 'Tree'
according to whether they represent a form for the edition or a list/tree for a global datas view.

A form can be asked by an action opening in mode 'Tree'. The form is open in the list mode (like the user push on
'switch view').

The domain
This notion allows you to regulate which ressourcesare visible in a selected view.(restriction)

For example, in the invoice case, you can define an action that opens a view that shows only invoices not paid.

The domains are written in python; list of tuples. The tuples have three elements;

• the field on which the test must be done

05/02/08
25

• the operator used for the test (<, >, =, like)


• the tested value

For example, if you want to obtain only 'Draft' invoice, use the following domain; [('state','=','draft')]

In the case of a simple view, the domain define the ressources which are the roots of the tree. The other ressources,
even if they are not from a part of the domain will be posted if the user develop the branches of the tree.

05/02/08
26

4.1.3 Sequences
Two objects are defined to generate sequences of numbers;

• ir.sequence: Sequences of numbers


• ir.sequence.type: Type of sequences

These numbers are used, for example, to;

• generate the number of the next invoice,


• generate the number of the next refund,
• generate continuous numbers for batches, ...

Principle
The ressources which need a sequence number record a name and a code in the types of sequences. This operation is
generally done once by the installation, at the first start of the server.

Several sequences can be defined for every type. When the ressource (invoice, location, ...) needs a sequence number,
this one is generated by an active sequence corresponding to the asked type.

The fields
Sequence Name
the name of the sequence,
Sequence Code
the defined code in the types of sequences
Active
As all forms having an 'active' case, the numbers of sequences can be activated or unactivated notching or not
this case.
Next Number
The next number
Increment Number
The step for the sequence

Example;

• prefix = LOT
• nextnumber = 1000
• suffix = TS
• increment = 10

The next generated number will be 'LOT1000TS'. The next one will be 'LOT1010TS', ...

05/02/08
27

4.1.4 Requests

Objects defined
res_request
The requests
res_request_link
The objects linked to requests
res_request_history
The history of all the requests

Principle
Sometimes, you just wish that an action inform a user that something has happened. When you have to do this, just
use the request object. This object allows you to send small pieces of information to any user about ongoing events. It
also allows you to link an object to the request you wish to sent.

The fields
In the table request you can find the following fields:

name
The request description
active
Activate or deactivate the request
req_ref
The request reference
req_type
The type of the request, must be one of the following: 'remind' or 'info'.
req_priority
The priority of the request. It must be one of the following: '0', '1' or '2', the higher the number the higher the
priority.
req_summary
A small summary of the request
state
The state of the request, must be one of the following: 'draft', 'wait', 'active', 'close'.
act_from
the user who emitted the request
act_to
the user who received the request
act_title
the action's result
act_date
the when the action was triggered
ref_partner_id
the partner the request is about
ref_doc1
the first reference for the request
ref_doc2
the second reference for the request
req_history
all the request history

05/02/08
28
Creating a request is just as simple as (taken from the project module) :

request = self.pool.get('res.request')
request.create(cr, uid,
{'name' : "Task '%s' closed" % task.name,
'req_ref' : 'project:task:%d' % task.id,
'req_type' : 'info',
'req_priority' : str(task.priority),
'state' : 'wait',
'req_summary' : "Task '%s' closed by %s" % (task.name, task.user_id.name),
'act_from' : task.user_id.id,
'act_to' : task.project_id.manager.id,
'ref_doc1' : 'project.task,%d'% (task.id,),
'ref_doc2' : 'project.project,%d'% (task.project_id.id,),
'act_title' : "Task '%s' closed" % (task.name,),
})

05/02/08
29

4.1.5 Customizing the menu


It's easy (but tricky) to grant grained access to menu based on the user's groups.

First of all, you should know that if a menu is not granted to any group then it is accessible to everybody ! If you want
to grant access to some groups just go to Menu > Administration > Menus > Grant Access to
menu and select the groups that can use this menu item.

Beware ! If the superuser does not belong to one of ther group, he will not be able to reach this menu again.

05/02/08
30
4.2 ModulesAdminCRM

The parameter setting of the module CRM allows you to define;

• the partners category


• the sales canals
• the customer's state of mind
• the case categories
• automatic segmentations

The partners categories


The categories are used to segment/classify the various partners. A same partner can be included in several categories.
These ones are defined according to the needs of the company.

Few examples;

• Customer Activity 1
• Very Important Customer
• Textile Supplier
• Newsletter Tiny ERP

The categories can be assigned manually (Newsletter, Textile Supplier) or automatically with the automatic
segmentations tool. An example would 'Very Important Customer', based on the amount of the partner's purchase.

Once the categories are defined, they can be used in the partners files.

05/02/08
31
The sale canals
The sale canals indicate the various methods of sale and communication between the partner and the company. Few
examples;

• Website / Email
• Shop 1
• Phone
• A residence

The canals are used when there are contacts with the partner in the form like the sale/ppurchase opportunities and the
Help−desk. You can, for example, make some analysis on different canals; sales pipeline, ...

The states of mind


The states of mind define a level of value on the partner option opposite to the company; Dissatisfied −> Normal −>
Satisfied. At every state of mind, we assign a selected value in this level. These values are making calculations on a
certain period.

Level of value:

Very dissatisfied 0.0


Dissatisfied 2.0
Normal 5.0
Satisfied 6.3
Very satisfied 10.0

In the different interactions with the customer, the Tiny ERP users can marked his state of mind according to their
contacts having with the customer. The program is able to calculate the staat of mind of the customer depending on
the last interactions with the same customer.

So, if the customer has made three orders.

2005/01/01 Order 1 Dissatisfied 2.0


2005/01/20 Sale opportunity 2 Normal 5.0
2005/03/10 Helpdesk 3 Very satisfied 10.0

The program can calculate, with the automatic segmentation, that the customer has a state of mind of 6.3. This number
is taking part of other parameters, the intervals between the tasks, the reductions, ...

This system can be very useful to send, for example, a promotion every month to the customers who are not happy.

The types of categories


These categories classify sales opportunities and the technical tasks of the Helpdesk.

Example of HelpDesk;

• Problem with the hardware,


• Problem of configuration,
• Bug, ...

05/02/08
32
Example of sales opportunities;

• Website,
• ERP integration,
• store, ...

05/02/08
33
4.3 ModulesAdminAccount
Three ressources must be configured for the accounting

• the account charges


• the various currencies used
• the various applicable taxes

05/02/08
34

4.3.6 ModulesAdminAccountChart
The best method to create a new account chart is to write it directly in the XML file. So, this one will be simply
reusable. With this intention, consult the section on the data download. Tiny ERP may used several account charts at
the same time. With this intention, we use some different views of the same accounts.

Into Tiny ERP, the accounts are structured hierarchically with the type of accounts 'VIEW'. These can't be used in the
transactions, they are only used to structure the real accounts.

So, it is possible to make two different views of the same accounts. Here is a simple example;

• Shop 1 (*)
♦ Actives Mg 1 (*)
◊ Fortis Bank 1
◊ Cash 1
♦ Incomes Mg 1
♦ Expenses Mg 1
• Shop 2 (*)
♦ Actives Mg 2 (*)
◊ Fortis Bank 2
◊ Cash 2
♦ Incomes Mg 2
♦ Expenses Mg 2

These accounts may be presented differently with other accounts views but using the same real accounts; income,
expenses, money cash.

• Actives (*)
♦ Shop 1 (*)
◊ Fortis Bank 1
◊ Cash 1
• Shop 2 (*)
♦ Fortis Bank 2
♦ Cash 2
• Incomes (*)
♦ Incomes Mg 1
♦ Incomes Mg 2
• Expenses (*)
♦ Expenses Mg 2
♦ Expenses Mg 2

In these representations, the accounts marked with (*) are from the type 'view'.

05/02/08
35
4.3.7 ModulesAdminAccountCurrency

The accounting is multi−currencies. As a preliminary every currencies must be defined with their rate relative to the
default one. An account can be only in one currency.

It is better to have an account tree by currency but you can mix several accounts of different currencies in the
accounting.

The used fields

Relative Change Rate


Relative change rate compared to the unity (in this case; the EURO).
Digits
Number of figures after the comma for the amounts written in this currency.
Computational accuracy
Precision of the amounts calculation in this currency.

05/02/08
36
4.3.8 ModulesAdminAccountTax
The taxes are used, by example, in the definition of a product or in the invoice lines.

The various taxes must be encode beforehand. There exists several modes of calculation for the taxes;

• Percentage
• Fixe
• Code Python
• None

The taxes can be applicable or not according to some criteria: partners, amount, number of pieces. Two application
tests are available;

• Always applicable
• Applicable according to a python code which will be executed for the test.

The dependencies
In some countries, the taxes can be composed or depended of another taxe. We can define a relation between the
different taxes. During a sale, if we specify a tax, the sub−taxes are also calculated.

It is possible to define a tax which regroup two others;

• Taxe on the screen


♦ VAT (21%)
♦ Recup (1.52 EUR)

On the invoice, you can specify 'Taxe on the screen' and the two taxes will automatically get inside the accounting.
You have to define:

|Name of the taxe|Type|Amount|Parent| |Taxe on the screen|None|0|| |VAT (21%)|Percentage|0.21|Taxe on the screen|
|Recup (1.52 EUR)|Fixed|1.52|Taxe on the screen|

You can also use this system if one tax must be ventilated on several accounts.

The accounts
Two accounts are mandatory for the taxes; paid and collected taxes. These two may be identical for the same tax or
not.

Taxes domain
Some taxes are only effective under certain forms. We define then their own domain.

For example, the auction houses in Belgium are subjected at the law of the margin sale. The VAT is not deductible
and can be invoiced with sale expenses. (percentage of adjudication). These taxes are then defined with the domain
'auction'. They are only used by the auction module.

The calculated taxes


Some taxes are more difficul than a simple multiplication or addition. It is possible to transfer a python code that
counts the value of the applicable taxes on the unit amount and/or a another python code that determines if the tax is

05/02/08
37
applicable or not.

The value of the variable result is used as the amount of the tax and as the boolean indicating if the tax must be
computed. Some informations are accessible:

• price_unit : the unit price of the product


• address : the address partner or False

___Example___; the SACEM impose royalties on the works allocated more than 2500 EUR of 10% and of 5% if the
price is over 50.000 EUR.

Code to calculate the taxe;

result = 0.0
if price_unit>=50000:
»··result=price_unit*0.05
elif price_unit>=2500:
»··result=price_unit*0.1

Test code if applicable;

result = price_unit>=2500

___Example___; The screen tax to calculate a tax pc with python_code

05/02/08
38

The compute code to calculate tax (at the end the result variable is the tax):

result =0.0
if price_unit>100:
result = 5 + price_unit * 0.06
else:
result= price_unit * 0.10

To define the tax in Main Menu go : Menu/Definitions/Accounting/Tax.

05/02/08
39

4.3.9 ModulesAdminAccountImputations
Sale order

Incomes account: product preference or the one by default


Taxe account: taxe collected from the taxe definition
Credit account: partners preferences

Purchase order

Expense account: product preference or the one by default


Taxe account: taxe paid from the taxe definition
Flow account: partners preferences

Inventory

Stock account: product preference (inventory account)


Stock losses account: product preference
Stock benefit account: product preference

The movements

The accounts inventory from the movements are taken in account only in the case of an exit ot an entry in the location
that belongs to the company. The internal movements don't generate transactions.

Stock account: product preference (inventory account)


Stock losses account: product preference
Stock benefit account: product preference

A payment

Credit/flow account: partner preferences


Cash account: manual

05/02/08
40

4.4 ModulesAdminStock
Introduction to the stock management
Tiny ERP introduce a new concept for the stock management. As for the accounting, the stock management uses a
double−entry system; there is no batches apparition in the stock but only moves from a location to another. The stock
manages the suppliers and the customers.

So, when you order merchandise to the supplier, the supplier location receives automaticaly the batch. When you
receive the merchandise of the supplier the batch moves from the supplier location to the "My Stock" location.

Moreover, the stock management of Tiny ERP is represented by a tree. We can deliver baches in a specify warehouse
or deliver batches in the rack 3 that is situated at the level 2 of the warehouse. Even, the suppliers and customers
locations may be structured according to their geographical localization, their volume, ...

This representation allows you to manage uneasy situations in other ERP;

• location of materiels
• consigned stock
• stored merchandises of our suppliers

To create a structural location


The location are structured in tree. Thanks to the use of the double−entry system, you can/must also structure the
customers and suppliers stock.

As an exemple we will use this use case:

• different suppliers
• different customers with some of them renting merchandise
• two warehouses
♦ one with two stages
♦ the other with a consigned stock in the warehouse 1 of the carrefour customer

A structure that can manage these situations;

• suppliers
♦ textile suppliers
♦ it suppliers
• customers
♦ european customers
♦ non european customers
• company
♦ rented materials
♦ warehouse 1
◊ output
◊ stock 1
⋅ level 1
⋅ level 2
◊ stock 2
⋅ carrefour stock
⋅ others materials

05/02/08
41

You can obviously make prints or stock valorisations on each account (called locations).

The warehouses
Tiny ERP is multi−warehouses. Into Tiny ERP, the batches are stored in the locations. The warehouses define which
locations are in a warehouse. A warehouse uses 3 different locations (that may be the same or not).

1. the stock location; indicates the stock warehouse


2. the output location; indicates the place where the batches are getting out
3. the input location; indicates the place where arrive the batches

An example of the location structure for a warehouse;

• Warehouse
♦ OUTPUT
♦ STOCK
◊ Level 1
⋅ Room 1.1
⋅ Room 1.2
◊ Level 2
◊ INPUT

05/02/08
42

4.5 ModulesAdminSale
Tiny ERP is multi points of sale. A point of sale is different from a warehouse. It is a place where the sale transactions
are done, a warehouse is the article location and may be different from a point of sale.

The attributes
Shop Name
the name of a point of sale
Pricelist
the pricelist by default of this point of sale. This list is partner dependent.
Project
A point of sale can be associated to a project. (not mandatory)
Default Payment
The default payment method for this point of sale. This method is used in several circumstances; into an order
form for this point of sale, if any method of payment is specified, this one will be used in this POS by default.
Account Payment
All methods of payment accepted for this POS. A method of payment is an account, generally with the cash
type of your account chart.
Warehouse

05/02/08
43

A POS is associated to a warehouse. When there is an order in this POS, the merchandise is delivered from
this location.

05/02/08
44

4.6 Subscriptions

Modules that plan to use subscription


All modules that plans to use subscription with references to their documents have to fill in these tables:

• subscription.document: describe objects that can be referenced.


• subscription.document.fields: describe fields of this object that have to be changed when copying.

Example:

account_subscription.xml:

<?xml version="1.0"?>
<terp>
<data>
<delete model="subscription.document" search="[]"/>
=======================================================
Subscription Views
=======================================================
<record model="subscription.document" id="doc_invoice">
<field name="name">Invoice</field>
<field name="model">account.invoice</field>
</record>
<record model="subscription.document.fields">

05/02/08
45

<field name="name">Invoice Date</field>


<field name="field">date_invoice</field>
<field name="value">date</field>
<field name="document_id" ref="doc_invoice"/>
</record>
<record model="subscription.document.fields">
<field name="name">Invoice Due Date</field>
<field name="field">date_due</field>
<field name="value">date</field>
<field name="document_id" ref="doc_invoice"/>
</record>

</data>
</terp>

05/02/08
46

4.7 Audittrail
Module Audittrail
TinyERP gives you the opportunity to trace any call (read/write/create/unlink) to the database. This can be usefull to
check who tempered with the stock or deleted invoices.

How it works: when registering an audittrail rule, TinyERP replaces the traced method by a method that will first
create an audittrail log line and then execute the regular method. Audittrail rules can be registered for any object
(except audittrail.log) on all or any specific user(s)

For example, to trace any modification on invoices, go the the administration menu −> Audittrail −> Rules

Let's create a new rule named "test" which will record any write/create/delete on account.invoice for users "admin"
and "demo".

05/02/08
47

To make a rule active (so that it logs events) its state has to be "Subscribed"

(don't forget to make a few modifications on an invoice to feed the logs in this test)

Now, if we take a look at the administration menu −> Audittrail −> Logs, and search for all log lines we will see all
modifications made to invoices

05/02/08
48

If you subscribe a rule on, an object that is part of a module which normally loads after audittrail, don't forget to
update Audittrail depends to include that module or next time the server starts, the rule's state will be set back to
draft

If you make changes on a subscribed rule, unsubcribe it and subcribe it again after having saved the changes or
those changes won't apply

05/02/08
49

5 Architecture

05/02/08
50

5.1 Program structure


The core of the sever is formed by the following modules:

• An object database (ORM)


• A workflow system integrated with the Object database
• A wizard system
• A report system (based on reportlab)
• A service to export the objects (XML−RPC)

Server/client, XML−RPC
Tiny ERP is a based on a client/server architecture. They communicate using the XML−RPC protocol. XML−RPC is
a very simple protocol which allows the client to do remote procedure calls. The function called, his arguments, and
the result are transported using HTTP and encoded using XML.

for more infos on XML−RPC, consult:http://www.xml−rpc.com/

Client
The logic of Tiny ERP is configured on the server side. The client is very simple; it is only used to post datas (forms,
lists, trees) and to send back the result to the server. The updates and the addition of new functionalities don't need the
clients to be frequently upgraded, it makes Tiny ERP easier to maintain.

The client doesn't understand what he posts. Even actions like 'Click on the print icon' are sent to the server to ask how
to react.

The client operation is very simple; when a user makes an action (save a form, open a menu, print, ...) he sends this
action to the server. This one sends to the client the new action to execute.

There are three types of action;

• Open a window (form or tree)


• Print a document
• Execute a wizard

05/02/08
51

5.2 Objects
All Tiny ERP ressources are objects: menus, actions, reports, invoices, partners, ... Tiny ERP is based on a object
relational mapping of a database to control the information. Object names are hierarchical, as in the following
examples:

• account.transfer : a money transfer


• account.invoice : an invoice
• account.invoice.line : an invoice line

Generally, the first word is the name of the module: account, stock, sale.

Other advantages of an ORM;

• simpler relations : invoice.partner.address[0].city


• objects have properties and methods: invoice.pay(3400 EUR),
• inheritance, high level constraints, ...

It is easier to manipulate one object (example, a partner) than several tables (partner address, categories, events, ...)

The Physical Objects Model of TinyERP version 3.0.3

05/02/08
52

05/02/08
53

PostgreSQL
The ORM of Tiny ERP is constructed over PostgreSQL. It is thus possible to query the object used by Tiny ERP using
the object interface or by directly using SQL statements.

But it is dangerous to write or read directly in the PostgreSQL database, as you will shortcut important steps like
constraints checking or workflow modification.

The Physical Database Model of TinyERP

• TinyERP version 3.2.0rc2


• TinyERP version 3.2.0rc1
• TinyERP version 3.0.3
• TinyERP version 2.1.3

05/02/08
54

5.3 Views
Views are a way to represent the objects on the client side. It indicates to the client how to lay out the data coming
from the objects on the screen.

There are two types of views:

• form views
• tree views

Lists are simply a particular case of tree views.

The same object may have several views; thus, the products have several different views according to the product
variants or not, the first defined view of a kind will be used as the default for its kind, that way you can have a default
tree view (that will as the view of a one2many) and a specialized view with more or less information that will appear
when some double click on a menu item. Views are described in XML.

If no view has been defined for an object, the object is able to generate a view to represent itself. This can limit the
developper's work but result in less ergonomic views.

Usage example
When you open an invoice, here is the chain of operations followed by the client:

• An action asks to open the invoice (it gives the datas on the object (account.invoice), the view, the domain
(only invoices not paid)
• The client asks (with XML−RPC) to the server what views are defined for the invoice object and what are the
datas he must show.
• The client displays the form according to the view

05/02/08
55

To develop new objects


The design of new objects is restricted to the minimum: create the objects and optionally create the views to represent
them. The PostgreSQL tables do not have to be written by hand because the objects are able to automatically create
them (or adapt them in case they already existed).

05/02/08
56

5.4 Workflows
The objects and the views allow you to define very simply new forms, lists/trees and interactions between them. But it
is not enough you have to define the dynamics of these objects.

Few examples:

• a confirmed sale order must generate an invoice, according to certain conditions


• an paid invoice must, only under certain conditions, start the shipping order

The workflows describe with graphs these interactions. One or several workflows may be associated to the objetcs.
Workflow are not mandatory some objects don't have workflows.

Below, an example workflow use for sale orders. It must generate invoices and shippings according to some
conditions.

In this graph, the nodes represent the actions to do:

05/02/08
57

• create an invoice,
• cancel the sale order,
• generate the shipping order, ...

The arrows are the conditions;

• waiting for the order validation,


• invoice paid,
• click on the cancel button, ...

The squared nodes represent other Workflows;

• the invoice
• the shipping

05/02/08
58

5.5 Reports
Tiny ERP uses a flexible and powerfull reporting system. Reports are generated either in PDF or in HTML. Reports
are designed on the principle of separation between the data layer and the presentation layer.

We first generate an XML file containing all the data needed by the report. This file is then processed using an XSL
stylesheet to produce an RML file which, in turn, is processed to produce either a PDF document or an HTML file.

05/02/08
59

5.6 Services
Services are python code methods. Some services are grouped

List of services
Service: workflow
• Object: workflow.wkf_service.workflow_service
♦ Methods
◊ workflow_service.trg_write
◊ workflow_service.trg_delete
◊ workflow_service.trg_create
◊ workflow_service.trg_validate

Services group: web−services


• Service: object_proxy
♦ Object: osv.osv.osv_pool
◊ Methods
⋅ osv_pool.obj_list
⋅ osv_pool.exec_workflow
⋅ osv_pool.execute
⋅ osv_pool.execute_cr
• Service: common
♦ Object: service.web_services.common
◊ Methods
⋅ common.ir_get
⋅ common.ir_set
⋅ common.ir_del
⋅ common.about
⋅ common.login
• Service: object
♦ Object: service.web_services.objects_proxy
◊ Methods
⋅ objects_proxy.execute
⋅ objects_proxy.exec_workflow
• Service: wizard
♦ Object: service.web_services.wizard
◊ Methods
⋅ wizard.execute
⋅ wizard.create
• Service: report
♦ Object: service.web_services.report_spool
◊ Methods
⋅ report_spool.report
⋅ report_spool.report_get
⋅ report_spool.report_check

05/02/08
60

Services group: report


• Service: report.custom
♦ Object: report.custom.report_custom
◊ Methods
⋅ report_custom.create
⋅ report_custom.args_get
⋅ report_custom.result
• Service: report.workflow.instance.graph
♦ Object: base.ir.workflow.print_instance.report_graph
◊ Methods
⋅ report_graph.create
⋅ report_graph.args_get
⋅ report_graph.result
• Service: report.account.recall.receivable
♦ Object: account.report.report_recall.report_custom
◊ Methods
⋅ report_custom.create
⋅ report_custom.args_get
⋅ report_custom.result
• Service: report.account.journal
♦ Object: account.report.print_journal.report_custom
◊ Methods
⋅ report_custom.create
⋅ report_custom.args_get
⋅ report_custom.result
• Service: report.hr.timesheet.allweeks
♦ Object: hr.report.timesheet.report_custom
◊ Methods
⋅ report_custom.create
⋅ report_custom.args_get
⋅ report_custom.result
• Service: report.hr.timesheet.bymonth
♦ Object: hr.report.bymonth.report_custom
◊ Methods
⋅ report_custom.create
⋅ report_custom.args_get
⋅ report_custom.result
• Service: report.crm.case
♦ Object: crm.report.report_businessopp.report_custom
◊ Methods
⋅ report_custom.create
⋅ report_custom.args_get
⋅ report_custom.result
• Service: report.project.tasks.gantt
♦ Object: project.report.gantt_report.report_tasks
◊ Methods
⋅ report_tasks.create
⋅ report_tasks.args_get
⋅ report_tasks.result
• Service: report.project.project.gantt
♦ Object: project.report.gantt_report.report_projects
◊ Methods
⋅ report_projects.create

05/02/08
61

⋅ report_projects.args_get
⋅ report_projects.result
• Service: report.res.partner.address
♦ Object: report.interface.report_rml
◊ Methods
⋅ method report_rml.create
⋅ method report_rml.args_get
⋅ method report_rml.result
• Service: report.res.partner.ids
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.account.invoice
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.account.invoice.list
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.account.transfer
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.network.material.list
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.network.network.list
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.network.network.list2
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.stock.packing.list
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create

05/02/08
62

⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.stock.shipping.list
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.stock.lot.overview
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.stock.location.overview
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.purchase.order
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.sale.order
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result
• Service: report.project.report
♦ Object: report.interface.report_rml
◊ Methods
⋅ report_rml.create
⋅ report_rml.args_get
⋅ report_rml.result

Service: wizard.res.partner.sms_send
• Object: base.res.partner.wizard.wizard_sms.part_sms
♦ Methods
◊ part_sms.execute

Service: wizard.res.partner.spam_send
• Object: base.res.partner.wizard.wizard_spam.part_email
♦ Methods
◊ part_email.execute

05/02/08
63

Service: wizard.res.partner.clear_ids
• Object: base.res.partner.wizard.wizard_clear_ids.wiz_clear_ids
♦ Methods
◊ wiz_clear_ids.execute

Service: wizard.account.journal
• Object: account.wizard.wizard_journal.wiz_journal
♦ Methods
◊ wiz_journal.execute

Service: wizard.account.move.line.reconcile
• Object: account.wizard.wizard_reconcile.wiz_reconcile
♦ Methods
◊ wiz_reconcile.execute

Service: wizard.account.invoice.split
• Object: account.wizard.wizard_invoice_split.wiz_reconcile
♦ Methods
◊ wiz_reconcile.execute

Service: wizard.account.invoice.refund
• Object: account.wizard.wizard_refund.wiz_refund
♦ Methods
◊ wiz_refund.execute

Service: wizard.account.move.line.check
• Object: account.wizard.wizard_verify.wiz_check
♦ Methods
◊ wiz_check.execute

Service: wizard.hr.si_so
• Object: hr.wizard.sign_in_out.wiz_si_so
♦ Methods
◊ wiz_si_so.execute

Service: wizard.campaign.partner.email_send
• Object: marketing.campaign.wizard.wizard_campaign_email.part_email
♦ Methods
◊ method part_email.execute

05/02/08
64

Service: name:wizard.campaign.partner.add
• Object: marketing.campaign.wizard.wizard_campaign_partner_add.part_add
♦ Methods
◊ part_add.execute

Service: wizard.campaign.partner.sms_send
• Object: marketing.campaign.wizard.wizard_campaign_sms.part_sms
♦ Methods
◊ part_sms.execute

Service: wizard.project.wiz_bill
• Object: project.wizard.billing.wiz_bill
♦ Methods
◊ wiz_bill.execute

Service: wizard.project.wiz_copy
• Object: project.wizard.proj_copy.wiz_copy
♦ Methods
◊ wiz_copy.execut

Service: wizard.project.wiz_copy
• Object: project.wizard.proj_copy.wiz_copy
♦ Methods
◊ wiz_copy.execute

Service: wizard.project.wiz_deactivate
• Object: project.wizard.proj_inactivate.wiz_deactivate
♦ Methods
◊ wiz_deactivate.execute

05/02/08
65

6 Objects

05/02/08
66

6.1 Introduction
All the ERP datas are accessible as "objects". As an exemple, there is a res.partner object to access the datas
concerning the partners, an account.invoice object for the ones concerning the invoices, etc...

Please note that there is an object for every type of ressource, and not an object per ressource. We have thus a
res.partner object to manage all the partners and not a res.partner object per partner. If we talk in "oriented
object" terms, we could also say that there is an object per level.

The direct consequences is that all the objects methods have a common parameter: the "ids" parameter. This one
specify on which ressources (for example: on which partner) the method must be applicated. Precisily, this parameter
contains a list of ressource ids on which the method must be applied.

For example, if we have two partners with the identifiers 1 and 5, and we want to applicate the res_partner's
method "send_email", we will write something like:

res_partner.send_email(... , [1, 5], ...)

We will see the exactly syntax of object method calls further in this document.

In the following operation, we will see how to define a new object. Then, we will check out the different methods to
do it.

05/02/08
67

6.2 Definition
To define a new object, you have to describe a new python class. This class must inherit from the osv class in the osv
module.

The first line of the object definition will always be of the form:

class name_of_the_object(osv.osv):
...

An object is defined by declaring some fields with predefined names in the class. Two of them are required (_name
and _columns), the rest is optional. The predefined fields are:

_auto
Default value: True.
_columns (required)
The object fields. See the fields section for details.
_constraints
The constraints on the object. See the constraints section for details.
_defaults
The default values for some of the object's fields. See the default value section for details.
_name (required)
Name of the object. Default value: None.
_table
Name of the SQL table. Default value: the value of the _name field above of which the dots ( . ) are replaced
by underscores ( _ ).
_rec_name
Name of the field in which the name of every ressource is stored. Default value: 'name'. Note: by default, the
name_get method simply returns the content of this field.
_order
Name of the fields used to sort the results of the search and read methods. Default value: 'id'.
_inherits
The list of osv objects the object inherits from. This list must be given in a python dictionary of the form:
{'name_of_the_parent_object': 'name_of_the_field', ...}. Default value: {}.
_sequence
Name of the SQL sequence that manages the ids for this object. Default value: None.
_sql
SQL code executed upon creation of the object (if _auto is True)
_log_access
Determine whether or not the write access to the ressource must be logged. If true, four fields will be created
in the SQL table: create_uid, create_date, write_uid, write_date. Those fields represent respectively the id of
the user who created the record, the creation date of record, the id of the user who last modified the record,
and the date of that last modification. This data may be obtained by using the perm_read method.

05/02/08
68

6.2.1 Fields
Objects may contain different types of fields. Those types can be divided into two categories: simple types and
relation types. The simple types are integers, floats, booleans, strings, etc. The relation types are used to represent
relations between objects.

Here is the list of the available types:

boolean
a boolean (true, false)
integer
an integer
float
a floating point number. The optional parameter digits define the precision and scale of the number. The scale
being the number of digits after the decimal point whereas the precision is the total number of significant
digits in the number (before and after the decimal point). If the parameter digits is not present, the number will
be a double precision floating point number. Warning: these floating−point numbers are inexact (not every
value can be converted to their representation) and this can lead to rounding errors. You should always use the
digits parameter for monetary amounts.

Example:
'rate' : fields.float('Relative Change rate',digits=(12,6)),

char
a string of limited length. The size parameter determines its size.
text
a text field with no limit in length.
date
a date
datetime
allows to store a date and the time of day in the same field.
binary
a binary chain
function
a field whose value is calculated by a function (rather than being stored in the database).

Parameters: fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None,


method=False,
where

• type is the function return type


• method whether the field is computed by a method (of an object) or a global function
• fcnt is the function or method that will compute the field value.
If method is true, the signature of the method must be:
def fnct(self, cr, uid, ids, field_name, arg, context)
otherwise (if it is a global function), it should be:
def fnct(cr, table, ids, field_name, arg, context)
Either way, it must return a dictionary of values of the form {id: value, ...}

selection
a field which allows the user to make a selection between various predefined values

Format of the selection parameter: tuple of tuples of strings of the form:


(('key_or_value', 'string_to_display'), ... )

05/02/08
69

Example:
'state': fields.selection((('n','Unconfirmed'),('c','Confirmed')),'State', required=True)

one2one

Parameters: obj

many2one

Parameters: obj
Optional parameters:

• ondelete: What should happen when the resource this field points to is deleted.

Predefined value: "cascade"


Default value: "null"
Example:
'commercial': fields.many2one('res.users', 'Commercial'),
one2many

Parameters: obj, fields_id


Example:
'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'),

many2many

Parameters: obj, rel, id1, id2


where

• obj is the other object which belongs to the relation


• rel is the table that makes the link
• id1 and id2 are the fields' names used in the relation table

Example:
'category_id':
fields.many2many(
'res.partner.category',
'res_partner_category_rel',
'partner_id',
'category_id',
'Categories'),

reference

Parameters: string, selection, size


where

• string is the English name of the field


• selection is either:
♦ a function that returns a list of tuple
♦ a list of tuples designating which model the field may reference to.

Example:
def _links_get(self, cr, uid):
cr.execute('select object,name from res_request_link order by priority')

05/02/08
70

return cr.fetchall()
...
'ref':fields.reference('Document Ref 2', selection=_links_get, size=128),
...

Note: If you wish to use the reference field in one of your modules, you must add your object to the list of
referencable objects. This action is done using a few lines of xml as in:
<record model="res.request.link">
<field name="name">Project Task</field>
<field name="object">project.task</field>
</record>

Common parameters to all field types


change_default
Whether or not the user can define default values on other fields depending on the value of this field.

Default value: False.


Example: (in res.partner.address)
'zip': fields.char('Zip', change_default=True, size=24),
In this case, it means users will be able to set default values for any field of the contact form depending on the value of
the 'zip' field. For example, the user could have the program automatically set the city field to 'Brussels' if the zip is
1200 and to 'Namur' if the zip is 5000.

readonly
Whether the field is editable or not.

Default value: False.

required
Whether the field is required or not. The program will refuse to save a resource if a required field is left blank.

Default value: False.

states
This parameter permits to define attributes for this field that will only be available in some states of the
resource.

Format: {'name_of_the_state': list_of_attributes}

where list_of_attributes is a list of tuples of the form [('name_of_attribute', value), ...]


Default value: {}.
Example: (in account.transfer)
'partner_id': fields.many2one('res.partner', 'Partner', states={'posted':[('readonly',True)]}),

string
The label of the field.

Default value: 'unknown'.


Example:
'tested': fields.boolean('Tested'),
Note: Strings containing non−ASCII characters must use python unicode objects.
Example: (in french)
'tested': fields.boolean(u'Testé'),

05/02/08
71

translate
Whether or not the content of this field should be translated (ie managed by the translation system).

Default value: False.

size

Default value: None.

priority

Default value: 0.

domain

Default value: [].

invisible

Default value:

context

Default value: ''.

selection

Default value: None.

05/02/08
72

6.2.2 Default values


The special attribute _defaults allows you to define default values for one or several fields of an object.

Because default values do not have to be static (they could, for example, depend on the values of resources already
created), you have to define "default values functions" and not simple "scalar" default values.

To define default values for an object fields, you have to define a dictionary of the form: {'name_of_the_field':
function, ...}

These functions take 4 parameters (obj, cr, uid, context) and can return any simple type (boolean, integer, string, etc.).

• obj: the osv object corresponding to the type of resource being created
• cr: a database cursor
• uid: the user ID of the person creating the record
• context: the current context (given by the client)

Default values are usually declared by using python's anonymous functions (also known as lambda functions). If you
want more information about lambda functions, please refer to the python tutorial or python manual.

Example
Here is the declaration of default values for the "sale.order" object:

_defaults = {
'date_order': lambda *a: time.strftime('%Y−%m−%d'),
'state': lambda *a: 'draft',
'user_id': lambda obj, cr, uid, context: uid
}

05/02/08
73

6.2.3 Constraints
Tiny ERP objects can optionally have custom integrity/validity constraints. In case such a constraint is transgressed,
the data modification which broke the constraint is cancelled and an error message appears on the screen.

Format
Constraints can be defined by using the _constraints attribute. If defined, it should be a list of tuples (a tuple per
constraint) of the form:

[(method, 'error message', list_of_field_names), ...]

where

• method is an object method used to check the constraint. This method must have the following signature:

def _name_of_the_method(self, cr, uid, ids): −> True|False

• error message is the error message displayed to the user if the constraint check fails.
• list_of_field_names is the list of the names of the fields that will be added to the error message. It should help
the user understand why the constraint check failed.

Example
Here is the definition of the integrity constraint for the object "account.move":

def _constraint_sum(self, cr, uid, ids):


cr.execute('SELECT a.currency_id
FROM account_move m, account_move_line l, account_account a
WHERE m.id=l.move_id AND l.account_id=a.id AND m.id IN ('+','.join(map(str, ids))+')
GROUP BY a.currency_id')
if len(cr.fetchall())>=2:
return True
cr.execute('SELECT abs(SUM(l.amount))
FROM account_move m LEFT JOIN account_move_line l ON (m.id=l.move_id)
WHERE m.id IN ('+','.join(map(str, ids))+')')
res = cr.fetchone()[0]
return res<0.01

_constraints = [
(_constraint_sum, 'Error: the sum of all amounts should be zero.', ['name'])
]

05/02/08
74

6.2.4 Methods
All objects define a set of methods as we will see in the section "6.3.2 predefined methods". Of course, you can also
add your own methods to a particular object.

To define such a method you only have to add it to source of the python class you're writing.

All the defined methods on an object are accessible from the outside (other objects or RPC), but don't forget the
python convention that makes private method prefixed with an underscore

05/02/08
75

6.2.5 Example
Here is an example of the definition of an object. More particulary, here is the definition of the object res.partner.

class res_partner(osv.osv):
def _credit_get(self, cr, uid, ids, prop, unknow_none, unknow_dict):
res={}
for id in ids:
acc = ir.ir_get(cr, uid, [('meta','res.partner'),('name','account.receivable')],[
cr.execute('select sum(amount) from account_move_line where account_id=%d and pa
res[id]=cr.fetchone()[0] or 0.0
return res

def _credit_search(self, cr, uid, obj, name, args):


if not len(args):
return []
where = ' and '.join(map(lambda x: '(sum(amount)'+x[1]+str(x[2])+')',args))
cr.execute('select partner_id from account_move_line where account_id in (select id fr
res = cr.fetchall()
if not len(res):
return [('id','=','0')]
return [('id','in',map(lambda x:x[0], res))]

def _debit_get(self, cr, uid, ids, prop, unknow_none, unknow_dict):


res={}
for id in ids:
acc = ir.ir_get(cr, uid, [('meta','res.partner'),('name','account.payable')],[
cr.execute('select sum(amount) from account_move_line where account_id=%d and
res[id]=cr.fetchone()[0] or 0.0
return res

def _debit_search(self, cr, uid, obj, name, args):


if not len(args):
return []
where = ' and '.join(map(lambda x: '(sum(amount)'+x[1]+str(x[2])+')',args))
cr.execute('select partner_id from account_move_line where account_id in (select id fr
res = cr.fetchall()
if not len(res):
return [('id','=','0')]
return [('id','in',map(lambda x:x[0], res))]

_name = "res.partner"
_auto=True
_columns = {
'name': fields.char('Name', size=64, required=True),
'name2': fields.char('Corporation Type', size=64),
'parent_id': fields.many2one('res.partner','Main Company'),
'commercial': fields.many2one('res.users','Commercial'),
'child_ids': fields.one2many('res.partner', 'parent_id', 'Partner Ref.'),
'ref': fields.char('Identifiant', size=64),
'lang':fields.char('Langage',size=2),
'vat':fields.char('VAT',size=32),
'bank':fields.char('Bank account',size=64),
'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', 'p
'events': fields.one2many('res.partner.event', 'partner_id', 'events'),
'credit': fields.function(_credit_get, fnct_search=_credit_search, method=True, string
'debit': fields.function(_debit_get, fnct_search=_debit_search, method=True, string='D
}

def name_search(self, cr, user, name, args=[], operator='ilike'):


ids = self.search(cr, user, [('name',operator,name)]+ args)

05/02/08
76

6.2.6 Inheritancy
Objects may be inherited in some custom or specific modules. It is better to inherit an object to add/modify
some fields.

It is done with:

_inherit='object.name'

Example:
class custom_material(osv.osv):
_name = 'network.material'
_inherit = 'network.material'
_columns = {
'manuf_warranty': fields.boolean('Manufacturer warranty?'),
}
_defaults = { 'manuf_warranty': lambda *a: False, }

custom_material()

In this example, the 'custom_material' will add a new field 'manuf_warranty' to the object 'network.material'.
You can also modify the definition of an already existing field from the parent object.

05/02/08
77

ids += self.search(cr, user, [('ref','=',name)]+ args)


return self.name_get(cr, user, ids)

def _email_send(self, cr, uid, ids, email_from, subject, body, on_error=None):


emails = self.read(cr, uid, ids, ['email'])
for email in emails:
if email['email']:
tools.email_send(email_from, email['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, {'uid':uid, 'name':'Send Partner Emai
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(ma
res = cr.fetchall()
adr = dict(res)
result = {}
for a in adr_pref:
result[a] = adr.get(a, adr.get('default', res[0][1]))
return result
res_partner()

05/02/08
78

6.3 Access

05/02/08
79

6.3.1 Access types


There are several ways to execute methods on the objects. All the ways share a common point: you have to get an
access point first.

The different available ways are:

* to access it directly
* by the netservice
* by xmlrpc

Direct access (direct reference towards the object)


In the osv's object methods, we may reach another object with the get method of the pool attribute.

self.pool.get('name_of_the_object')

for example:

partner_object = self.pool.get('res.partner')

Calling a method on the partner object we just recovered has the following form:

partner_object.name_of_the_method(parameters_for_that_method)

This kind of access is only possible inside objects, and thus, locally on the server.

Ref: osv/orm.py

Netservice access
Outside of an object but inside the server nevertheless (for example in the reports and wizards), we can reach an object
access point with the command:

service = netsvc.LocalService("object_proxy")

Calling an object method will then have the form:

result = service.execute(user_id, object_name, method_name, parameters)

Ref: osv/osv.py

XML−RPC access
From the network (LAN or internet), to reach an access point to the objects use the following command:

sock = xmlrpclib.ServerProxy('http://server_address:port_number/xmlrpc/object')

and the call to the method itself is made by:

result = sock.execute(user_id, password, object_name, method_name, parameters)

Réf: service/web_services.py

05/02/08
80

6.3.2 Predefined Methods


The predefined methods may be classified into 5 categories: the basic methods, methods to manipulate default values,
methods to get the permissions, methods to return the fields and views, and finally those naming the ressource.

Basic methods (create, search, read, browse, write, unlink)

create
Description
Create a new ressource
Signature
def create(self, cr, uid, vals, context={})
Parameters

• vals: a dictionary of values for every field. This dictionary must use this form: {'name_of_the_field': value,
...}
• context (optional): the actual context.

Returns
the id of the newly created resource.

search
Description
Search all the resources which satisfy certain criteria
Signature
def search(self, cr, uid, args, offset=0, limit=2000)
Parameters

• args: a list of tuples containing the search criteria. This list must be of the form: [('name_of_the_field',
'operator', value), ...]. The available operators are:
♦ =, >, <, <=, >=
♦ like, ilike
♦ child_of
• offset (optional): does not return the first offset results.
• limit (optional): the maximum number of results to gat back.

Returns
the list of ids of matching resources

read
Description
List of fields ressources values.
Signature
def read(self, cr, uid, ids, fields=None, context={})
Parameters

• ids: the list ressources identifiers to read.


• fields (optionnal): the list of the interested fields. If we don't introduce something for this parameter, the
function check all the fields.
• context (optional): the actual context.

05/02/08
81

Returns
A list of dictionaries (a dictionary per resource asked) of the form [{'name_of_the_field': value, ...}, ...]

browse
Description
Return one or several ressources with the objects form. These object fields can be reached directly with the
pointed notation ("object.name_of_the_field"). The "relations" fields are also automatically evaluated to allow
you to recover the values in the "neighbors" objects.
Example
Let's take the case of a partner (object 'res.partner') and of a partner contact (object 'res.partner.address'). Let's
suppose that we know the identifier of a partner contact (name contact_id) and we want to recover his name
and the account number of the company he works for.

Knowing that the object res.partner contains (amongst other things) the field

'bank':fields.char('Bank account',size=64),

and the object res.partner.address contains (amongst other thing) the fields

'partner_id': fields.many2one('res.partner', 'Partner', required=True),


'name': fields.char('Contact Name', size=64),

the most simple way to procced is to use the browse method:

addr_obj = self.pool.get('res.partner.address').browse(cr, uid, contact_id)

so, to recover the two fields that interest us, you have to write:

nom = addr_obj.name
compte = addr_obj.partner_id.bank

Signature
def browse(self, cr, uid, select, offset=0, limit=2000)
Parameters

• select: this parameter accept the datas of several types:


♦ a figure, identifier of a ressource
♦ a list if figures (liste of identifiers)
• offset (optional): the number of result to pass.
• limit (optional): the maximum number of results to return.

Returns

• if an integer (identifier) has been passed as select parameter, return an object having the properties described
here above.
• if a list of integer (identifiers) has been passed, return the object list.

Remarque
This method is only useful locally (on the server itself) and not with the other intefaces !!

05/02/08
82

write
Description
Writes values in one or several fields of one or several ressources
Signature
def write(self, cr, uid, ids, vals, context={})
Parameters

• ids: the ressources identifiers list to modify.


• vals: a dictionary with values to write. This dictionary must be with the form: {'name_of_the_field': value,
...}.
• context (optional): the actual contaxt.

Returns
True

unlink
Description
Delete one or several ressources
Signature
def unlink(self, cr, uid, ids)
Parameters

• ids: the identifiers ressources list to delete.

Returns
True

Methods to manipulate the default values(default_get,


default_set)
default_get
Description
Get back the value by default for one or several fields.
Signature
def default_get(self, cr, uid, fields, form=None, reference=None)
Parameters

• fields: the fields list which we want to recover the value by default.
• form (optional): TODO
• reference (optional): TODO

Returns
dictionary of the default values of the form {'field_name': value, ... }

default_set
Description
Change the default value for one or several fields.
Signature

05/02/08
83

def default_set(self, cr, uid, field, value, for_user=False)


Parameters

• field: the name of the field that we want to change the value by default.
• value: the value by default.
• for_user (optional): boolean that determinates if the new default vlaue must be available only for the current
user or for all the users.

Returns
True

Methods to manipulate the permissions (perm_read,


perm_write)
perm_read
Description
Signature
def perm_read(self, cr, uid, ids)
Parameters

• ids: an integer list

Returns
a list of dictionaries with the following keys

• level : access level


• uid : user id
• gid : group id
• create_uid : user who created the ressource
• create_date: date when the ressourece was created
• write_uid : last user who change the ressource
• write_date : date of the last change to the ressource

perm_write
Description
Signature
def perm_write(self, cr, uid, ids, fields)
Parameters
Returns

Methods to generate the fields and the views


(fields_get, distinct_field_get, fields_view_get)
fields_get
Description
Signature

05/02/08
84

def fields_get(self, cr, uid, fields = None, context={})


Parameters

• fields: a list of fields that interest us, if None, all the fields
• context: context['lang']

Result

fields_view_get
Description
Signature
def fields_view_get(self, cr, uid, view_id=None, view_type='form',
context={})
Parameters
Result

distinct_field_get
Description
Signature
def distinct_field_get(self, cr, uid, field, value, args=[], offset=0,
limit=2000)
Parameters
Result

Methods concerning the name of the ressources


(name_get, name_search)
name_get
Description
Signature
def name_get(self, cr, uid, ids, context={})
Parameters
Result
a list of tuples of the form [(id, name), ...]
Example (in res.partner.address)

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']):
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

05/02/08
85

name_search
Description
Signature
def name_search(self, cr, uid, name='', args=[], operator='ilike',
context={})
Parameters
Result

05/02/08
86

To use a specific method to an object is making by the same way of the predefined methods. The different types of
access sawn before stay available.

It is good to keep the uses of Python;

• the methods beginning with '_' are private

05/02/08
87

6.3.4 ObjectsWritingValues

Object writing Values


One2many
Argument:

[(ID1, ID2, ID3)]

Where

• ID1: the type of operation (link, create, remove, ...)


• ID2: the ID of the ressource (in case of a modification or unlink)
• ID3: the values

Different values for ID1:

• 0: create
• 1: write
• 2: unlink
• 6: set the link to a list of IDs (ID3)

Example:

Example in XML files


<record model="account.invoice.line">
<field name="name">Installation and Training, 8 hours</field>
<field name="price_unit">75.00</field>
<field name="quantity">8</field>
<field name="invoice_line_tax_id" eval="[(6,0,[tva21])]"></field>
</record>

where tva21 is the ref of a tax previously defined (id="tva21").

Example in objects:
Link:

self.create(cr, uid, {'invoice_line_tax_id': [(6, 0, [1234])]})

Create:

self.create(cr, uid, {'invoice_line_tax_id': [(0,0, {'name':'Hello', 'Price':45.6})]})

05/02/08
88

6.4 Property Fields


Properties have been introduced in version 3.4 of Tiny ERP to replace the deprecated ir_set system.
Properties are special fields of ressources that describe

Declaring a property

A property is a special field: fields.property.


class res_partner(osv.osv):
_name = 'res.partner'
_inherit = 'res.partner'
_columns = {
'property_product_pricelist': fields.property(
'product.pricelist',
type='many2one',.
relation='product.pricelist',·
string="Sale Pricelist",·
method=True,
view_load=True,
group_name="Pricelists Properties"),
}
res_partner()

Then you have to create the default value in a .XML file for this property:

<record model="ir.property" id="property_product_pricelist">


<field name="name">property_product_pricelist</field>
="[('model','=','res.partner'),
<field name="fields_id" search
('name','=','property_product_pricelist')]"/>
<field name="value" eval="'product.pricelist,'+str(list0)"/>
</record>

TIP: if the default value points to a ressource from another module, you can use the ref function like this:
<field name="value" eval="'product.pricelist,'+str(ref('module.data_id'))"/>

Putting properties in forms


To add properties in forms, just put the <properties/> tag in your form. This will automatically add all
properties fields that are related to this object. The system will add properties depending on your rights.
(some people will be able to change a specific property, others not).

Properties are displayed by section, depending on the group_name attribute. (It is rendered in the client like a
separator tag).

05/02/08
89
How does this work ?

The fields.property class inherits from fields.function and override the read and write method. The type of this
field is many2one, so in the form a property is represented like a many2one function.

But the value of a property is stored in the ir.property class/table as a complete record. The stored value is a
field of type reference (not many2one) because each property may points to a different object. If you edit
properties values (from the administration menu), these are represented like a field of type reference.

When you read a property, the program gives you the property attached to the instance of object you are
reading. It this object has no value, the system will give you the default property.
The definition of a property is stored in the ir.model.fields class like any other fields. In the definition of the
property, you can add groups that are allowed to change to property.

Using properties or normal fields

When you want to add a new feature, you will have to choose to implement it as a property or as normal field.
Use a normal field when you inherit from an object and want to extend this object. Use a property when the
new feature is not related to the object but to an external concept.

Here are a few tips to help you choose between a normal field or a property:

Normal fields extend the object, adding more features or data.

A property is a concept that is attached to an object and have special features:

• Different value for the same property depending on the company


• Rights management per field
• It's a link between ressources (many2one)

Example 1: Account Receivable


The default "Account Receivable" for a specific partner is implemented as a property because:

• This is a concept related to the account chart and not to the partner, so it is an account property that is
visible on a partner form. Rights have to be managed on this fields for accountants, these are not the
same rights that are applied to partner objects. So you have specific rights just for this field of the
partner form: only accountants may change the account receivable of a partner.

• This is a multi-company field: the same partner may have different account receivable values depending
on the company the user belongs to. In a multi-company system, there is one account chart per
company. The account receivable of a partner depends on the company it placed the sale order.

• The default account receivable is the same for all partners and is configured from the general property
menu (in administration).
Note: one interresting think is that properties avoid spagetthi codes. The account module depends on the
partner (base) module. But you can install the partner (base) module without the accounting module. If you
add a field that points to an account in the partner object, both objects will depend on each other. It's much
more difficult to maintain and code (for instance, try to remove a table when both tables are pointing to each
others.)

05/02/08
90

Example 2: Product Times


The product expiry module implements all delays related to products: removal date, product usetime, ... This
module is very useful for food industries.

This module inherits from the product.product object and add new fields to it:

class product_product(osv.osv):
_inherit = 'product.product'
_name = 'product.product'
_columns = {
'life_time': fields.integer('Product lifetime'),
'use_time': fields.integer('Product usetime'),
'removal_time': fields.integer('Product removal time'),
'alert_time': fields.integer('Product alert time'),
}
product_product()

This module add simple fields to the product.product object. We did not used properties because:

• We extend a product, the life_time field is a concept related to a product, not to another object.
• We do not need a right management per fields, the different delays are managed by the same people
that manage all products.

05/02/08
91

7 Data loading

05/02/08
92

7.1 Introduction
At the Tiny ERP installation, two steps are necessary to create and feed the data base:

1. Create the SQL tables


2. Insert the different datas in the tables

The creation (or modification in the case of an upgrade) of SQL tables is automated thanks to the description of
objects in the server.

Into Tiny ERP, all the logic of the application is stored in the data base. We find for example:

• the definitions of the reports,


• the object default values,
• the form description of the interface client,
• the relations between the menu and the client buttons, ...

There must be a mecanism to describe, modify and reload the different datas. These datas are represented into a set of
XML files that can eventually be loaded at the start of the program in order to fill in the tables.

05/02/08
93

7.2 Files loading


Introduction
All the modules of the program must be accompanied with __terp__.py file in its main directory. Here is an
example of a __terp__.py file for the account module.

{
"name" : "Tiny TERP Accounting",
"version" : "0.1",
"depends" : ["base"],
"init_xml" : [ "account_workflow.xml", "account_data.xml"],
"update_xml" : [ "account_view.xml", "account_report.xml"],
}

The fields
name
The name of the module, an arbitrary english name.

version
The version of the module. This version is not the same version as the Tiny ERP version. The modules evolve
indenpendently of the program.

depends
The list of modules this module depends upon. The dependencies are very important because they determinate the
order in which the different modules are loaded.

For example, if the workflow of the sale order (module:sale) uses the workflow of an invoice (module:account), it is
important to create the account workflow before the sale order workflow.

init_xml
The XML files loaded when the server is started with the argument: −−init=module.

Generally, there is:

• the description of workflows (convention: modulename_workflow.xml)


• some usefull data (convention: modulename_data.xml)
• demonstration data (convention: modulename_demo.xml)

update_xml
The XML files to loaded when the server is started with the argument: −−update=modulename. If the server is
executed with the argument −−init=modulename, the files 'update_xml' are also downloaded.

We generally find:

• the views description (convention: modulename_view.xml)

05/02/08
94

• the reports description (convention: modulename_report.xml)

05/02/08
95

7.3 XML files structure


The XML files have the following structure:

<?xml version="1.0"?>
<terp>
<data>
...
</data>
</terp>

05/02/08
96

7.3.1 The data

record tag
The addition of new data is made with the record tag. This one takes a mandatory attribute : model. Model is the
object name where the insertion has to be done. The tag record can also take an optional attribute: id. If this attribute is
given, a variable of this name will be able to be used later on in the same file to make reference to the new created
ressource ID.

A record tag may contain field tags. They indicate the record's fields value. If a field is not specified the default
value will be used.

Example
<record model="ir.actions.report.xml" id="l0">
<field name="model">account.invoice</field>
<field name="name">Invoices List</field>
<field name="report_name">account.invoice.list</field>
<field name="report_xsl">account/report/invoice.xsl</field>
<field name="report_xml">account/report/invoice.xml</field>
</record>

field tag
The attributes that may conatain the field tag are the followings:

name
mandatory attribute indicating the field name
eval
python expression that indicating the value to add
ref
reference to an id defined in this file

function tag
model
name
eval
should evaluate to the list of parameters of the method to be called, excluding cr and uid

Example
<function model="ir.ui.menu" name="search" eval=""/>

getitem tag
Takes a subset of the evaluation of the last child node of the tag.

type
int or list
index
int or string (a key of a dictionary)

05/02/08
97

Example
Evaluates to the first element of the list of ids returned by the function node

<getitem index="0" type="list">


<function model="ir.ui.menu" name="search" eval=""/>
</getitem>

05/02/08
98

7.3.2 IR Use
The ir_set tag allows you to insert new values in the information repository. This tag must contain several field tags.

field tags
attributes: name and eval

The attributes are those defined by the access methods to the information repository. We must provide him with
several attributes: keys, args, name, value, // isobject, replace, meta. optional fields

Example
<ir_set>
<field name="keys" eval="[('action','client_print_multi'),('res_model','account.invoice')]"/>
<field name="args" eval="[]"/>
<field name="name">Print Invoices</field>
<field name="value" eval="'ir.actions.report.xml,'+str(l0)"/>
<field name="isobject" eval="True"/>
<field name="replace" eval="False"/>
</ir_set>

05/02/08
99

7.4 Importing from a CSV

Instead of using .XML file, you can import .CSV files. It is simpler but the migration system does not migrate
the data imported from the .CSV files. It is like the noupdate attribute in .XML files. It is also more difficult to
keep track of relations between resources and it is slower at the installation of the server.

Use this only for demo data that will never been upgraded from one version of Tiny ERP to another.

The name of the object is the name of the file before the first dot. You must use one file per object to import.
For example, to import a file with partners (including their multiple contacts and events), the file must be
named like one of the following example:

• res_partner.csv
• res_partner.tiny_demo.csv
• res_partner.tiny.demo.csv

For the name of the object, use underscores (_) instead of dots. (.)

Structure of the CSV file

Have a look at the user manual for a complete description on how to construct your .CSV file.

Usefull info:

• Separator of field: ,
• Quote of fields: "
• Encoding to use: UTF-8

Export demo data and import it from a module

You can import .CSV file that have been exported from the Tiny ERP client. This is interesting to create your
own demo module. But both formats are not exactly the same, mainly due to the conversion: Structured Data
-> Flat Data -> Structured Data.

• The name of the column (first line of the .CSV file) use the end user term in his own language when you
export from the client. If you want to import from a module, you must convert the first column using the
fields names. Example, from the partner form:

Name,Code,Contacts/Contact Name,Contacts/Street,Contacts/Zip
becomes
name,ref,address/name,address/street,address/zip

05/02/08
100

• When you export from the Tiny ERP client, you can select any many2one fields and their children
relation. When you import from a module, Tiny ERP tries to recreate the relation between the two
resources. For example, do not export something like this from a sale order form - otherwise Tiny ERP
will not be able to import your file: Order
Description,Partner/Name,Partner/Payable,Partner/Address/Name

• To find the link for a many2one or many2many field, the server use the name_search function when
importing. So, for a many2one field, it is better to export the field 'name' or 'code' of the related resource
only. Use the more unique one. Be sure that the field you export is searchable by the name_search
function. (the 'name' column is always searchable). Order Description,Partner/Code

• Change the title of the column for all many2many or many2one fields. It's because you export the
related resource and you import a link on the resource. Example from a sale order: Partner/Code should
become partner_id and not partner_id/code.

• Many2many fields. If all the exported data contains 0 or 1 relation on each many2many fields, there will
be no problem. Otherwise, the export will result in one line per many2many. The import function expect
to get all many2many relations in one column, separated by a comma. So, you have to make to
transformation. For example, if the categories "Customer" and "Supplier" already exists :

name,category_id Smith,"Customer,Supplier"

If you want to create these two categories you can try :


name,category_id
Smith,"Customer,Supplier"
This does not work as expected: a category "Customer, Supplier" is created. The solution is to create an
empty line with only the second category:
name,category_id
Smith,Customer
,Supplier

(Note the comma before "Supplier").

• Readonly fields. Do not try to import readonly fields like the amount receivable or payable for a partner.
Otherwise, Tiny ERP will not accept to import your file.

• Exporting trees. You can export and import tree structures using the parent field. You just have to take
care of the import order. The parent have to be created before his children.

Use record id like in xml file:


It's possible to define an id for each line of the csv file. This allow to define references between records:

id, name, parent_id:id


record_one, Father,
record_two, Child, record_one

05/02/08
101

7.5 XML data files convention


The ressources are placed in different files according to their uses. By convention;

modulename_workflow.xml the definitions of workflows


modulename_view.xml the views
modulename_data.xml the important datas to download
modulename_report.xml the reports declarations
modulename_demo.sql the useful datas for the demo version

The workflow files have to be loaded before the datas ! Otherwise, the ressource created won't be integrated inside the
workflow because the later is not yet defined.

05/02/08
102

7.6 Managing updates

Managing updates and migrations


Tiny ERP has a built'in migration and upgrade system which allows updates to be nearly (or often) automatic. This
system also allows to easily incorporate custom modules.

Table/Object structure
When you run tinyerp−server with option −−init or −−update, the table structure are updated to match the new
description that is in .py files. Fields that are removed are not removed in the postgresql database not to lose data.

So, simply running −−update or −−init, will upgrade your table structure.

It's important to run −−init=module the first time you install the module. Next time, you must use the
−−update=module argument instead of the init one. This is because init loads ressources that are loaded only once and
never upgraded (eg: ressources with no id="" attribute or within a noupdate="1" <data> tag).

Data
Some data is automatically loaded at the installation of Tiny ERP:

• views, actions, menus,


• workflows,
• demo data

This data is also migrated to a new version if you run −−update or −−init.

Workflows
Workflows are also upgraded automatically. If some activities are removed, the documents states evolves
automatically to the preceding activities. That ensure that all documents are always in valid states.

You can freely remove activities in your XML files with the unlink method. If workitems are in this activity, they will
evolve to the preceding unlinked activity. And after the activity will be removed.

Things to care about during development


Since version 3.0.2 of Tiny ERP, you can not use twice the same 'id="..."' during resource creation in your XML files,
unless they are in two different modules.

Resources which don't contain an id are created (and updated) only once; at the installation of the module or when you
use the −−init argument.

If a resource has an id and this resource is not present anymore in the next version of the XML file, Tiny ERP will
automatically remove it from the database. If this resource is still present, Tiny ERP will update the modifications to
this resource.

If you use a new id, the resource will be automatically created at the next update of this module.

Use explicit id declaration !, Example:

05/02/08
103

• view_invoice_form,
• view_move_line_tree,
• action_invoice_form_open, ...

It is important to put id="...." to all record that are important for the next version migrations. For example, do not
forget to put some id="..." on all workflows transitions. This will allows Tiny ERP to know which transition has been
removed and which transition is new or updated.

Custom modules
For example, if you want to override the view of an object named 'invoice_form' in your xml file (id="invoice_form").
All you have to do is redefine this view in your custom module with the same id. You can prefix ids with the name of
the module to reference an id defined in another module.

Example:

<record model="ir.ui.view" id="account.invoice_form">


...
<record>

This will override the invoice form view. You do not have to delete the old view, like in 3.0 versions of Tiny ERP.

Note that it is often better to use view inherytancy instead of overwritting views.

In this migration system, you do not have to delete any ressource. The migration system will detect if it is an update or
a delete using id="..." attributes. This is important to preserve references duing migrations.

Demo datas
Demo datas do not have to be upgraded; because they are probably modified, deleted, ... by users. So, to avoid demo
data to be upgraded, you can put a noupdate="1" attribute in the <data> tag of your .xml data files.

05/02/08
104

8 Views
Views are used to represent objects on the client side.

05/02/08
105

8.1 Introduction
As all data of the program is stored in objects, as explained in the Objects section, how are these objects exposed to
the user ? We will try to answer this question in this section.

First of all, let's note that every resource type uses its own interface. For example, the screen to modify a partner's data
is not the same as the one to modify an invoice.

Then, you have to know that the Tiny ERP user interface is dynamic, it means that it is not described "statically" by
some code, but dynamically built from XML descriptions of the client screens.

From now on, we will call these screen descriptions views.

A notable characteristic of these views is that they can be edited at any moment (even during the program execution).
After a modification to a displayed view has occurred, you simply need to close the tab corresponding to that 'view'
and re−open it for the changes to appear.

Views principles
Views describe how each object (type of resource) is displayed. More precisely, for each object, we can define one (or
several) view(s) to describe which fields should be drawn and how.

There are two types of views:

1. form views
2. tree views

Form views
The field disposition in a form view always follows the same principle. Fields are distributed on the screen following
the rules below:

• By default, each field is preceded by a label, with its name.


• Fields are placed on the screen from left to right, and from top to bottom, according to the order in which they
are declared in the view.
• Every screen is divided into 4 columns, each column being able to contain either a label, or an "edition" field.
As every edition field is preceded (by default) by a label with its name, there will be two fields (and their
respective labels) on each line of the screen. The green and red zones on the screen−shot below, illustrate
those 4 columns. They designate respectively the fields and their labels.

05/02/08
106

Views also support more advanced placement options:

• A view field can take the place of several columns. For example, the light blue zone on the screen−shot below
is, in fact, the only field of a "one to many". We will come back later on this note, but let's see that he takes
the whole width of the screen and not only one column.
• We can also make the opposite operation: take a columns group and divide it in as many columns as desired.
The surrounded blue zones of the screen above are good examples. Precisely, the blue framework up and on
the right side takes the place of two columns, but contains 4 columns.

As we can see it below in the orange zone of the screen, there is also a way to distribute the fields of an object on
different tabs.

05/02/08
107

Tree views
These views are used when we work in list mode (in order to visualize several resources at once) and in the search
screen. These views are simpler than the form views and thus have less options.

The different options of views will be detailed into the next section.

Tree views can define colors for each row of the tree. To do this, you have to add the attribute color like this sample;

<tree string="Invoice" colors="grey:state=='draft'">

Colors are separated by ';'.

<tree string="All tasks" color="red:date_deadline&lt;=current_date and state=='draft';grey:state=='cancel'">

05/02/08
108

8.2 File Format


The common structure to all the XML files of Tiny ERP is described in the XMLFiles section

The files describing the views are also of the form:

<?xml version="1.0"?>
<terp>
<data>

[view definitions]

</data>
</terp>

The view definitions contain mainly three types of tags:

• <record> tags with the attribute model="ir.ui.view", which contain the view definitions themselves
• <record> tags with the attribute model="ir.actions.act_window", which link actions to these views
• <menuitem> tags, which create entries in the menu, and link them with actions

New : You can precise groups for whom the menu is accessible using the groups attribute in menuitem tag.

New : You can now add shortcut using the shortcut tag. Example :

<shortcut name="Draft Purchase Order (Proposals)" model="purchase.order" logins="demo" menu="m"/>

Note that you should add an id attribute on the menuitem which is refered by menu attribute.

<record model="ir.ui.view" id="v">


<field name="name">sale.order.form</field>
<field name="model">sale.order</field>
<field name="type">form</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<form string="Sale Order">
...
</form>
</field>
</record>

Default value for the priority field : 16. When not specified the system will use the view with the lower priority.

Special tags
<form string="Sale Order"> ... </form>
The form tag defines a new view form, it takes a string attribute that defines the tab name for this view.
<notebook>
With notebooks you can distribute the view fields on different tabs (each one defined by a page tag). You can
use the tabpos properties to set tab at: up, down, left, right.
<page string="Order Line"> ... </page>
defines a new notebook page for the view.

• string: defines the name of the page.

05/02/08
109

<newline/>
force a return to the line even if all the columns of the view are not filled in.
<label string="Test"/>
adds a simple label using the string attribute as caption.
<separator string="Links" colspan="4"/>
add a separator line. The string attribute defines its label and the colspan attribute defines his horizontal size
(in number of columns).
<button/>
add a button using the string attribute as label. When clicked, it can trigger methods on the object, workflow
transitions or actions (reports, wizards, ...).

• string: define the button's label


• confirm: the message for the confirmation window, if needed. Eg: confirm="Are you sure?"
• name: the name of the function to call when the button is pressed. In the case it's an object function, it must
take 4 arguments: cr, uid, ids, *args.
♦ cr is a database cursor
♦ uid is the userID of the user who clicked the button
♦ ids is the record ID list
♦ *args is a tuple of additional arguments
• states: a comma−separated list of states (from the state field or from the workflow) in which the button must
appear. If the states attribute is not given, the button is always visible.
• type: this attribute can have 3 values
"workflow" (value by default): the function to call is a function of workflow
"object": the function to call is a method of the object
"action": call an action instead of a function

<group>
groups several columns and split the group in as many columns as desired.

• colspan: the number of columns to use


• ''rowspan':: the number of rows to use
• expand: if we should expand the group or not
• col: the number of columns to provide (to its children)
• string: (optional) If set, a frame will be drawn around the group of fields, with a label containing the string.
Otherwise, the frame will be invisible.

attributes for the "field" tag


select="1"
mark this field as being one of the research critera for this ressource search view.
colspan="4"
the number of columns on which a field must extend.
readonly="1"
set the widget as readonly
required="1"
the field is marked as required. If a field is marked as required, a user has to fill it the system won't save the
ressource if the field is not filled. This attribute superseed the required field value defined in the object.
nolabel="1"
hides the label of the field (but the field is not hidden in the search view).
string=""
change the field label. Note that this label is also used in the search view: see select attribute above).
domain
can restrict the domain.
Example: domain="[('partner_id','=',partner_id)]"

05/02/08
110

widget
can change the widget.
Example: widget="one2many_list"
on_change
define a function that is called when the content of the field changes.
Example: on_change="onchange_partner(type,partner_id)"
See ViewsSpecialProperties for details

<?xml version="1.0"?>
<terp>
<data>

...

<record model="ir.ui.view" id="v">


<field name="name">sale.order.form</field>
<field name="model">sale.order</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Sale Order">
<notebook>
<page string="Order Line">
<field name="name" select="1"/>
<group colspan="2" col="4">
<field name="shipped" select="1"/>
<field name="invoiced" select="1"/>
</group>
<newline/>
<field name="date_order" select="1"/>
<field name="shop_id" select="1" on_change="onchange_shop_id(s
<field name="partner_id" select="1" on_change="onchange_partne
<field name="order_policy" />
<field name="partner_contact_id" domain="[('partner_id','=',pa
<field name="pricelist_id" />
<field name="partner_invoice_id" domain="[('partner_id','=',pa
<field name="project_id" select="1"/>
<field name="partner_shipping_id" domain="[('partner_id','=',p
<newline/>
<field name="order_line" widget="one2many_list" colspan="4" string=""/>
<newline/>
<group colspan="4" col="7">
<field name="amount_untaxed"/>
<field name="amount_tax"/>
<field name="amount_total"/>
<button name="button_dummy" string="Compute" states="d
</group>
<group colspan="4" col="12">
<field name="state" select="1"/>
<button name="order_confirm" string="Confirm Order" st
<button name="cancel" string="Cancel Order" states="dr
<button name="invoice_cancel" string="Cancel Order" st
<button name="invoice_recreate" string="Recreate Invoi
<button name="invoice_corrected" string="Invoice Corre
<button name="ship_cancel" string="Cancel Order" state
<button name="ship_recreate" string="Recreate Delivery
<button name="ship_corrected" string="Delivery Correct
<button name="action_cancel_draft" states="cancel" str
<button name="manual_invoice" states="manual" string="
</group>
</page>
<page string="Payment">
<field name="payment_line" widget="one2many_list" colspan="4"

05/02/08
111

</page>
<page string="Notes">
<field name="note" colspan="3" string=""/>
</page>
<page string="Links">
<field name="invoice_id" select="1" colspan="4"/>
<field name="shipping_id" colspan="4"/>
</page>
</notebook>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="a">
<field name="name">sale.order</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.order</field>
<field name="view_type">form</field>
<field name="view_id" ref="v"/>
</record>
<menuitem name="Operations/Sales/Order" action="a"/>

...

</data>
</terp>

05/02/08
112

8.3 Special Properties


The on_change attribute defines a function that is called when the content of the view field has changed.

This method takes at least arguments: cr, uid, ids.

cr, uid and ids are the three classical arguments.

You can add parameters to the function, these parameters are defined in a view.

The example below is from the sale order view.

<field name="shop_id" select="1" on_change="onchange_shop_id(shop_id)"/>

def onchange_shop_id(self, cr, uid, ids, shop_id):


v={}
if shop_id:
shop=self.pool.get('sale.shop').browse(cr,uid,shop_id)
v['project_id']=shop.project_id.id
if shop.pricelist_id.id:
v['pricelist_id']=shop.pricelist_id.id
v['payment_default_id']=shop.payment_default_id.id
return {'value':v}

When editing the shop_id form field, the onchange_shop_id method of the sale_order object is called and
returns a dictionary where the 'value' key contains a dictionary of the new value to use in the 'project_id', 'pricelist_id'
and 'payment_default_id' fields.

Note that it is possible to change more than just the values of fields. For example, it is possible to change the value of
some fields and the domain of other fields by returning a value of the form: return {'domain': d, 'value': value}

context : in <record model="ir.actions.act_window" id="a"> you can add a context field, whic will be pass to the
action. See the example below :

<record model="ir.actions.act_window" id="a">

<field name="name">account.account.tree1</field>
<field name="res_model">account.account</field>
<field name="view_type">tree</field>
<field name="view_id" ref="v"/>
<field name="domain">[('code','=','0')]</field>
<field name="context">{'project_id': active_id}</field>

</record>

05/02/08
113

8.4 Inheritancy in Views

Inheritancy in views
When you create and inherit objects in some custom or specific modules, it is better to inherit (than to replace) from
an existing view to add/modify/delete some fields and preserve the others.

Example:

<record model="ir.ui.view" id="view_partner_form">


»···<field name="name">res.partner.form.inherit</field>
»···<field name="model">res.partner</field>
»···<field name="inherit_id" ref="base.view_partner_form"/>
»···<field name="arch" type="xml">
»···»···<notebook position="inside">
»···»···»···<page string="Relations">
»···»···»···»···<field name="relation_ids" colspan="4" nolabel="1"/>
»···»···»···</page>
»···»···</notebook>
»···</field>
</record>

The inheritancy engine will parse the existing view and search for the the root nodes of "<field name="arch"
type="xml">". It will append or edit the content of this tag. If this tag has some attributes, it will look for the matching
node, including the same attributes (unless position).

This will add a page to the notebook of the res.partner.form view in the base module.

You can use these values in the position attribute:

• inside (default): your values will be appended inside this tag


• after: add the content after this tag
• before: add the content before this tag
• replace: replace the content of the tag.

Second Example:

<record model="ir.ui.view" id="view_partner_form">


»···<field name="name">res.partner.form.inherit</field>
»···<field name="model">res.partner</field>
»···<field name="inherit_id" ref="base.view_partner_form"/>
»···<field name="arch" type="xml">
»···»···<page string="Extra Info" position="replace">
»···»···»···<field name="relation_ids" colspan="4" nolabel="1"/>
»···»···</page>
»···</field>
</record>

Will replace the content of the Extra Info tab of the notebook by one 'relation_ids' field.

The parent and the inherited views are correctly updated with −−update=all argument like any other views.

05/02/08
114

8.5 Graphs in views


A graph is a new mode of view for all views of type form. If, for example, a sale order line must be visible as
list or as graph, define it like this in the action that open this sale order line:

<field name="view_type">form</field>
<field name="view_mode">tree,graph</field>

Then, the user will be able to switch from one view to the other. Unlike forms and trees, Tiny ERP is not able
to automatically create a view on demand for the graph type. So, you must define a view for this graph:

<record model="ir.ui.view" id="view_order_line_graph">


<field name="name">sale.order.line.graph</field>
<field name="model">sale.order.line</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph string="Sales Order Lines">
<field name="product_id" group="True"/>
<field name="price_unit" operator="*"/>
</graph>
</field>
</record>

The graph view


A view of type graph is just a list of fields for the graph.

The first field is the X axis. The second one is the Y axis and the optionnal third one is the Z axis for 3
dimensional graphs. You can apply a few attributes to each field/axis:

• group: if set to true, the client will group all item of the same value for this field. For each other field, it
will apply an operator
• operator: the operator to apply is another field is grouped. By default it's '+'. Allowed values are:
º '+': addition
º '*': multiply
º '**': exponent
º 'min': minimum of the list
º 'max': maximum of the list

Defining real statistics on objects

The easiest method to compute real statistics on objects is:

1. Define a statistic object wich is a postgresql view


2. Create a tree view and a graph view on this object

You can get en example in all modules of the form: report_.... Example: report_crm.

TODO: be more complete here...

05/02/08
115

9 Workflow

05/02/08
116

9.1 Introduction
The workflow system in Tiny ERP is a very powerful mechanism that can describe the evolution of documents
(model) in time.

Workflows are entirely customizable, they can be adapted to the flows and trade logic of almost any company. The
workflow system makes Tiny ERP very flexible and allows it to easily support changing needs without having to
program new functionalities.

The workflow system has multiple goals:

• description of document evolution in time


• automatic trigger of actions if some conditions are met
• management of company roles and validation steps
• management of interactions between the different objects/modules
• graphical tool for visualization of document flows

To understand its utility, see these two examples:

• Discount on orders
• Sale order and invoice

05/02/08
117

9.2 Example of Workflows


• Edition of the order process
• Dependences of the invoices and orders

05/02/08
118

9.2.1 Discount on orders


The next diagram represent a very basic workflow of an order:

The order starts in the 'draft' state, when it is in redaction and not approved. When the user press on the 'Confirm'
button, the invoice is created and the order comes into the 'CONFIRMED' state.

Then, two operations are possible:

1. the order is done (shipped)


2. the order is canceled

Let's suppose a company has a need not implemented in TinyERP. For example, suppose their commercials can only
offer discounts of 15% or less. Every order having a discount above 15% must be approved by the commercial
manager.

This modification in the sale logic doesn't need any line of python code! A simple modification of the workflow
allows us to take this new need into account and add the extra validation step.

The workflow is thus modified as above and the orders will react as we want to. We then only need to modify the
order form view and add a validation button at the desired location.

05/02/08
119

We could then further improve this workflow by sending a request to the sale manager when an order enters the
'Validation' state. Workflow nodes can execute object methods; only two lines of Python are needed to send a request
asking the sale manager to validate or not the order.

05/02/08
120

9.2.2 Purchase order and Invoice


A sale order that generates an invoice and a shipping order.

05/02/08
121

05/02/08
122

9.3 Terminology
Workflow
A workflow is a graph of actions/conditions associated to a type of resource/document (model). You can have several
workflows defined for the same document; in that case, they are all executed at the same time.

Activity
Activities represent the different possible states of a document. They are the nodes of a workflow. Actions can
optionally be associated with each activity so that they are executed when a document enters into that activity.

Transition
Transitions are conditions to be satisfied in order to pass from one activity to another. Transitions are represented by
arcs between two activities. These conditions can be of several types:

• condition on the user role


• condition on another resource or workflow (trigger)
• condition on values of fields of this resource

Instance
An instance is the state of the workflow for a particular document.

Workitem
A workitem represents an activity being executed for a given instance. It is possible to have several active workitems
at the same time in a single workflow instance.

05/02/08
123

9.4 Define a Workflow


Three types of ressources are necessary to desrcibe a workflow;

• WKF: the workflow,


• WKF_ACTIVITY: the activities (nodes),
• WKF_TRANSITION: the transitions between the activities.

05/02/08
124

9.4.3 Workflow
A WKF resource represents a given workflow.

Its fields are :

• name: name of the workflow


• osv: resource model / type of document
• on_create: True if the workflow must be instantiated automatically when the OSV resource is created.

05/02/08
125

9.4.4 The nodes (activities)


The WKF_ACTIVITY resource represents the nodes of workflows. These nodes are the actions to be executed.

The fields
split_mode:

• XOR: One necessary transition, takes the first one found


• OR : Take only valid transitions (0 or more)
• AND: All the transitions are launched at the same time (fork)

In the OR and AND separation mode, certain workitems can be generated.

In the AND mode, the activity waits for all transitions to be valid, even if some of them are already valid. They are all
triggered at the same time.

join_mode:

• XOR: One transition necessary to continue to the destination activity.


• AND: Waits for all transition conditions to be valid to execute the destination activity.

kind:

The type of the activity can take several values:

• DUMMY: Do nothing
• FUNCTION: Execute the function selected by an action.
• SUBFLOW: Execute a sub−workflowSUBFLOW_ID. The action method must return the ID of the concerned
resource by the subflow ! If the action returns False, the workitem disappears !

A sub−workflow is executed when an activity is of the type SUBFLOW. This activity will end when the
sub−workflow will be finished. While the sub−workflow is active, the workitem of this activity is frozen.

action:

The action indicates the method to execute when a workitem comes into this activity. The method must be defined in a
object which belongs this workflow and have the following signature:

def object_method(self, cr, uid, ids):

05/02/08
126

In the action though, they will be called by a statement like:

object_method()

flow_start

Indicates if the node is a start node. When a new instance of a workflow is created, a workitem is activated for each
activity marked as a flow_start.

flow_stop

Indicates if the node is an ending node. When all the active workitems for a given instance come in the node marked
by flow_stop, the workflow is finished.

wkf_id

The workflow which this activity belongs to.

05/02/08
127

9.4.5 Transitions
Workflow transitions are the conditions to be satisfied to go from one activity to the next. They are represented by
one−way arrows joining two activities.

The conditions are from different types:

• role to satisfy by the user


• button pressed in the interface
• end of a subflow through a selected activity of subflow

The roles and signals are evaluated before the expression. If a role or a signal is false, the expression will not be
evaluated.

Transition tests may not write values in objects.

The fields
act_from

Source activity. When this activity is over, the condition is tested to determinate if we can start the ACT_TO activity.

act_to

The destination activity.

condition

Expression to be satisfied if we want the transition done.

signal

When the operation of transition comes from a button pressed in the client form, signal tests the name of the pressed
button.

If signal is NULL, no button is necessary to validate this transition.

role_id

The role that a user must have to validate this transition.

05/02/08
128

9.5 Expressions
Expressions are written as in python:

• True
• 1==1
• 'hello' in ['hello','bye']

Any field from the resource the workflow refers to can be used in these expressions. For example, if you were creating
a workflow for partner addresses, you could use expressions like:

• zip==1400
• phone==mobile

05/02/08
129

9.6 The notion of Roles


Roles can be attached to transitions. If a role is given for a transition, that transition can only be executed if the user
who triggered it possess the necessary role.

Each user can have one or several roles. Roles are defined in a tree of roles, parent roles having the rights of all their
children.

Example:

• CEO
♦ Technical manager
◊ Lead developper
⋅ Developpers
⋅ Testers
♦ Sales manager
◊ Commercials
◊ ...

Let's suppose we handle our own bug database and that the action of marking a bug as valid needs the Testers role. In
the example tree above, marking a bug as valid could be done by all the users having the following roles: Testers,
Lead developper, Technical manager, CEO.

05/02/08
130

9.7 Error handling


As of this writing, there is no exception handling in workflows.

Workflows being made of several actions executed in batch, they can't trigger exceptions. In order to improve the
execution efficiency and to release a maximum of locks, workflows commit at the end of each activity. This approach
is reasonable because an activity is only started if the conditions of the transactions are satisfied.

The only problem comes from exceptions due to programming errors; in that case, only transactions belonging to the
entirely terminated activities are executed. Other transactions are "rollbacked".

05/02/08
131

10 Reports

05/02/08
132

10.1 Introduction
There are mainly three types of reports in Tiny ERP: custom reports (made within the interface), RML reports, and
hand−coded reports. This documentation mainly concerns RML reports.

RML reports don't require programming but require two simple XML files to be written:

• a file describing the data to export (*.xml)


• a file containing the presentation rules to apply to that data (*.xsl)

05/02/08
133

10.2 XSL:RML reports


XSL:RML reports don't require programming (in python) but require writing an XML file and an XSL:RML style
sheet per report type.

The role of the XML template is to describe which fields of the resource have to be exported (by the server). The
XSL:RML style sheet deals with the layout of the exported data as well as the "static text" of reports. Static text is
referring to the text which is common for all reports of the same type (for example, the title of table columns).

Example
Here is, as an example, the different files for the simplest report in the ERP.

XML Template

<?xml version="1.0"?>
<ids>
<id type="fields" name="id">
<name type="field" name="name"/>
<ref type="field" name="ref"/>
</id>
</ids>

XML data file (generated)

<?xml version="1.0"?>
<ids>
<id>
<name>Tiny sprl</name>
<ref>pnk00</ref>
</id><id>
<name>ASUS</name>
<ref></ref>
</id><id>

05/02/08
134

<name>Agrolait</name>
<ref></ref>
</id><id>
<name>Banque Plein−Aux−As</name>
<ref></ref>
</id><id>
<name>China Export</name>
<ref></ref>
</id><id>
<name>Ditrib PC</name>
<ref></ref>
</id><id>
<name>Ecole de Commerce de Liege</name>
<ref></ref>
</id><id>
<name>Elec Import</name>
<ref></ref>
</id><id>
<name>Maxtor</name>
<ref></ref>
</id><id>
<name>Mediapole SPRL</name>
<ref></ref>
</id><id>
<name>Opensides sprl</name>
<ref>os</ref>
</id><id>
<name>Tecsas sarl</name>
<ref></ref>
</id>
</ids>

XSL stylesheet

<?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="ids"/>
</xsl:template>

<xsl:template match="ids">
<document>
<template pageSize="21cm,29.7cm">
<pageTemplate>
<frame id="col1" x1="2cm" y1="2.4cm" width="8cm" height="26cm"/>
<frame id="col2" x1="11cm" y1="2.4cm" width="8cm" height="26cm"/>
</pageTemplate>
</template>

<stylesheet>
<blockTableStyle id="ids">
<!−− title −−>
<blockFont name="Helvetica−BoldOblique" size="12" start="0,0" stop="−1,0"/>
<lineStyle kind="BOX" colorName="black" start="0,0" stop="−1,0"/>

<!−− frame around the table−−>


<lineStyle kind="BOX" colorName="black" start="0,0" stop="−1,−1"/>
</blockTableStyle>
</stylesheet>

05/02/08
135

<story>
<blockTable colWidths="2cm, 6cm" repeatRows="1" style="ids">
<tr>
<td t="1">Ref.</td>
<td t="1">Name</td>
</tr>
<xsl:apply−templates select="id"/>
</blockTable>
</story>
</document>
</xsl:template>

<xsl:template match="id">
<tr>
<td><xsl:value−of select="ref"/></td>
<td><para><xsl:value−of select="name"/></para></td>
</tr>
</xsl:template>
</xsl:stylesheet>

Resulting RML file (generated)

<?xml version="1.0"?>
<document>
...
<story>
<blockTable colWidths="2cm, 6cm" repeatRows="1" style="ids">
<tr>
<td t="1">Ref.</td>
<td t="1">Name</td>
</tr>
<tr>
<td>pnk00</td>
<td><para>Tiny sprl</para></td>
</tr>
<tr>
<td></td>
<td><para>ASUS</para></td>
</tr>
<tr>
<td></td>
<td><para>Agrolait</para></td>
</tr>
<tr>
<td></td>
<td><para>Banque Plein−Aux−As</para></td>
</tr>
<tr>
<td></td>
<td><para>China Export</para></td>
</tr>
<tr>
<td></td>
<td><para>Ditrib PC</para></td>
</tr>
<tr>
<td></td>
<td><para>Ecole de Commerce de Liege</para></td>
</tr>
<tr>
<td></td>
<td><para>Elec Import</para></td>
</tr>

05/02/08
136

<tr>
<td></td>
<td><para>Maxtor</para></td>
</tr>
<tr>
<td></td>
<td><para>Mediapole SPRL</para></td>
</tr>
<tr>
<td>os</td>
<td><para>Opensides sprl</para></td>
</tr>
<tr>
<td></td>
<td><para>Tecsas sarl</para></td>
</tr>
</blockTable>
</story>
</document>

Fore more information on the formats used:

• RML : http://reportlab.com/docs/RML_UserGuide_1_0.pdf
• XSL − Specification :http://www.w3.org/TR/xslt
• XSL − Tutorial :http://www.zvon.org/xxl/XSLTutorial/Books/Output/contents.html

All these formats use XML:

• http://www.w3.org/XML/

05/02/08
137

10.2.1 XML Template


XML templates are simple XML files describing which fields among all available object fields are necessary for the
report.

File format
Tag names can be chosen arbitrarily (it must be valid XML though). In the XSL file, you will have to use those
names. Most of the time, the name of a tag will be the same as the name of the object field it refers to.

Nodes without type attribute are transferred identically into the XML destination file (the data file). Nodes with a type
attribute will be parsed by the server and their content will be replaced by data coming from objects. In addition to the
type attribute, nodes have other possible attributes. These attributes depend on the type of the node (each node type
supports or needs different attributes). Most node types have a name attribute, which refers to the name of a field of
the object on which we work.

As for the "browse" method on objects, field names in reports can use a notation similar to the notation found in object
oriented programming languages. It means that "relation fields" can be used as "bridges" to fetch data from other
(related) objects.

Let's use the "account.transfer" object as an example. It contains a partner_id field. This field is a relation field
("many to one") pointing to the "res.partner" object. Let's suppose that we want to create a report for transfers and in
this report, we want to use the name of the recipient partner. This name could be accessed using the following
expression as the name of the field:

partner_id.name

Possible types
Here is the list of available field types:

• field: It is the simplest type. For nodes of this type, the server replaces the node content by the value of the
field whose name is given in the name attribute.
• fields: when this type of node is used, the server will generate a node in the XML data file for each unique
value of the field whose name is given in the name attribute.

Notes:

• This node type is often used with "id" as its name attribute. This has the effect of creating one node for each
resource selected in the interface by the user.
• The semantics of a node <node type="fields" name="field_name"> is similar to an SQL statement of the form
"SELECT FROM object_table WHERE id in identifier_list GROUP BY field_name" where identifier_list is
the list of ids of the resources selected by the user (in the interface).

• eval: This node type evaluate the expression given in the expr attribute. This expression may be any Python
expression and may contain objects fields names.
• zoom: This node type allows to "enter" into the resource referenced by the relation field whose name is given
in the name attribute. It means that its child nodes will be able to access the fields of that resource without
having to prefix them with the field name that makes the link with the other object. In our example above, we
could also have accessed the field name of the partner with the following:

<partner type="zoom" name="partner_id">


<name type="field" name="name"/>
</partner>

05/02/08
138

In this precise case, there is of course no point in using this notation instead of the standard notation below:

<name type="field" name="partner_id.name"/>

The zoom type is only useful when we want to recover several fields in the same object.

• function: returns the result of the call to the function whose name is given in the name attribute. This function
must be part of the list of predefined functions. For the moment, the only available function is today, which
returns the current date.
• call: calls the object method whose name is given in the name attribute with the arguments given in the args
attribute. The result is stored into a dictionary of the form {'name_of_variable': value, ... } and can be
accessed through child nodes. These nodes must have a value attribute which correspond to one of the keys of
the dictionary returned by the method.

Example:
<cost type="call" name="compute_seller_costs" args="">
<name value="name"/>
<amount value="amount"/>
</cost>

TODO: documenter format methode appellée def compute_buyer_costs(self, cr, uid, ids, *args):

• attachment: extract the first attachment of the resource whose id is taken from the field whose name is given
in the name attribute, and put it as an image in the report.

Example:
<image type="attachment" name="id"/>

Example
Here is an example of XML file:

<?xml version="1.0" encoding="ISO−8859−1"?>


<transfer−list>
<transfer type="fields" name="id">
<name type="field" name="name"/>
<partner_id type="field" name="partner_id.name"/>
<date type="field" name="date"/>
<type type="field" name="type"/>
<reference type="field" name="reference"/>
<amount type="field" name="amount"/>
<change type="field" name="change"/>
</transfer>
</transfer−list>

05/02/08
139

10.2.2 Introduction to RML


For more information on the RML format, please refer to the official Reportlab documentation.

• http://reportlab.com/docs/RML_UserGuide_1_0.pdf

Tips and tricks


It is possible to put tables in RML tables. This is can be useful in some situations because it is not possible to extend a
field on several columns (colspan in html).

05/02/08
140

10.2.3 XSL:RML Stylesheet


There are two possibilities to do a XSL style sheet for a report. Either making everything by yourself, or use our
predefined templates

Either freestyle or use corporate_defaults + rml_template

import rml_template.xsl

required templates:
− frames?
− stylesheet
− story

optional templates:

Translations
As Tiny ERP can be used in several langages, reports must be translatable. But in a report, everything mustn't be
translated: only the actual text and not the formatting codes. A field will be processed by the translation system if the
XML tag which surrounds it (whatever it is) has a t="1" attribute. The server will translate all the fields with such
attributes in the report generation process.

Useful links:
• RML UserGuide (pdf) (reportlab.com)
• XSL Tutorial (zvon.org)
• XSL Reference (zvon.org)
• XSL tutorial and references (W3Schools)
• XSL Specification (W3C)

Example (with corporate defaults):


<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="../../custom/corporate_defaults.xsl"/>
<xsl:import href="../../base/report/rml_template.xsl"/>
<xsl:variable name="page_format">a4_normal</xsl:variable>

<xsl:template match="/">
<xsl:call−template name="rml"/>
</xsl:template>

<xsl:template name="stylesheet">
</xsl:template>

<xsl:template name="story">
<xsl:apply−templates select="transfer−list"/>
</xsl:template>

<xsl:template match="transfer−list">
<xsl:apply−templates select="transfer"/>
</xsl:template>

<xsl:template match="transfer">
<setNextTemplate name="other_pages"/>

05/02/08
141

<para>
<b t="1">Document</b>: <i><xsl:value−of select="name"/></i>
</para><para>
<b t="1">Type</b>: <i><xsl:value−of select="type"/></i>
</para><para>
<b t="1">Reference</b>: <i><xsl:value−of select="reference"/></i>
</para><para>
<b t="1">Partner ID</b>: <i><xsl:value−of select="partner_id"/></i>
</para><para>
<b t="1">Date</b>: <i><xsl:value−of select="date"/></i>
</para><para>
<b t="1">Amount</b>: <i><xsl:value−of select="amount"/></i>
</para>

<xsl:if test="number(change)>0">
<para>
<b t="1">Change</b>: <i><xsl:value−of select="change"/></i>
</para>
</xsl:if>

<setNextTemplate name="first_page"/>
<pageBreak/>
</xsl:template>
</xsl:stylesheet>

Example (yourself):

05/02/08
142

10.3 Each reports with own header


The need: Sales Quotation is printed on different paper from other documents.

The general approach: (note that I'm not giving step by step instructions for just this function because in the
actual implementation it's part of a larger group of customizations).

It requires edits to server-side python, xml and rml. This was done in 4.1.0 but seems to be the same in 4.0.x.

• order.sxw is used as basis for quotation.sxw


• quotation.sxw can be made blank if order is not in 'draft' state
• quotation.rml produced in the standard way and put in an appropriate place
• add a new button to the Sales Order form, and link to the report (see
TechnicalDocumentation/ReportsAddNew) - this can be done in the custom directory

<report id="report_sale_quotation"
string="Print Booking"
model="sale.order"
name="sale.booking"
rml="custom/report/quotation.rml"
auto="True" />

• check that the button works and a report is shown - do this before messing with further customization
• edit custom/corporate_rml_header.rml

<pageTemplate> becomes <pageTemplate id="first">

(note that the string "first" shouldn't be messed with) and then make a second template and call it "second" or
something.

• edit quotation.rml

<pageTemplate id="first"> becomes <pageTemplate id="second">

• check that the button still works and a report is shown - should still work with the old template
• now edit tinyerp-server/bin/report/report_sxw.py (note that this is not update-safe)
º method _add_header(self, node) needs a slight change, which matches id attributes to the
pageTemplate tag

if tag.nodeType==tag.ELEMENT_NODE:
found = self._find_node(node, tag.localName)

becomes

05/02/08
143
if tag.nodeType==tag.ELEMENT_NODE:
# ++ if tag id non-null make pageTemplate id tags match
found = self._find_node(node, tag.localName)
if found:
if not tag.getAttribute('id') or
(tag.getAttribute('id') == found.getAttribute('id')):
found.parentNode.replaceChild(tag, found)

• now check that the two reports come out with different templates

05/02/08
144

10.4 Bar Codes

Barcodes in RML files


Barcodes can be generated using the <barcode> tag in RML files. The following formats are supported:

• codabar
• code11
• code128 (default if not 'code' specified'
• standard39
• standard93
• i2of5
• extended39
• extended93
• msi
• fim
• postnet

You can change the following attributes for rendering your barcode:

• 'code': 'char'
• 'ratio':'float'
• 'xdim':'unit'
• 'height':'unit'
• 'checksum':'bool'
• 'quiet':'bool'

Examples:

<barcode code="code128" xdim="28cm" ratio="2.2">SN12345678</barcode>

05/02/08
145

10.5 OpenOffice Report

RML Report
Introductions
You can design reports using OpenOffice directly. To do this, write a report in OpenOffice and save it as a .SXW.
(OpenOffice 1 default format).

To put datas in your report, you have to write expressions enclosed with a double bracket. These expressions are in
Python. Example: [[ 2*4+3 ]] will write 11 in the generated document. When the report is generated in Tiny ERP, a
variable named 'objects' is defined. It is the list of all selected objects in the ERP.

Will produce the following document:

Attach:report_oo_sample1.pdf

Expressions
Objects are used like any other objects. For example, if you are creating a report for invoices, when the report is
generated, you can use the variable 'objects' that represent the list of selected invoices. Lists (or one2many fields) can

05/02/08
146

be used like any other many2one relations. Except that if you put a list in a document, it will duplicate the flowable
for each element of the list.

Duplicated flowables are:

1. table: will duplicate lines for each element of the list


2. document: will duplicate the document
3. section: will duplicate the section.

Sections in OpenOffice are created using the red icons in the following screenshot. Sections can be nested.

Examples
If you are writing a report on invoices. You can do this:

[[ len(objects) or 'No Invoice' ]] Number of invoices to print or write 'No Invoice'


[[ objects.name ]] Name of the invoice (will duplicate the line for each invoice if in a table)
[[ objects.partner_id.name ]] One line per invoice with the name of the partner
[[ objects[0].name ]] Write the name of the first invoice
[[ objects[0].partner_id.name ]] Write the name of the partner of the first invoice

Lists of objects have some methods:

1. explodeIn('varname'): will explode the group (line of table, section, page) and set a variable named 'varname'
that you can use.

Example of using explodeIn


Subscribe report
To subscribe a report, put this in a .xml file;

<report
string="Partner List"
model="res.partner"
name="partner.list"
rml="base/res/partner/report/partner_list.rml"
/>

05/02/08
147

10.6 Reports TAGS

Code find in tags is python code.


The context of the code (the variable's values you can use) is the following:

python objects/variables, available when the report start:

"objects" the list of objects to be printed (invoices for example)

"data" comes from the wizard

"time" see python documentation.

"user" the user object launching the report.

python functions you can use:

"setlang('fr')" change the langage used in automated translation (fields...).

"repeatIn(list,varname)" repeat the template (whole doc. or current paragraph?) for each object in the list. Use
varname in the template's tags.

"setTag('para','xpre')" change the enclosing RML tag (usually 'para') by an other (xpre is a preformatted
paragraph), in the (converted from sxw)rml document (?)

"removeParentNode"

Useful tags:
[[ repeatIn(objects,'o') ]] objects to be printed

[[ repeatIn(o.invoice_line,'l') ]] print every line

[[ o.quantity * o.price ]] Operations are OK.

[[ '%07d' % int(o.number) ]] number formating

[[ reduce(lambda x obj: x+obj.qty , list , 0 ) ]] total qty of list (try "objects" as list)

[[ user.name ]] user name.

[[ setLang(o.partner_id.lang) ]] Localized printings

[[ time.strftime('%d/%m/%Y') ]] format=dd MM YYYY, check python doc for more about "%d", ...

[[ time.strftime(time.ctime()[0:10]) ]] [[ time.strftime(time.ctime()[-4:]) ]] prints only date.

05/02/08
148

[[ time.ctime() ]] it prints the actual date & time.


[[ time.ctime().split()[3] ]] prints only time

one more interesting tag: if you want to print out the creator of an entry (create_uid) or the last one who wrote
on an entry (write_uid) you have to add something like this to the class your report refers to:

'create_uid': fields.many2one('res.users', 'User', readonly=1)

and then in your report it's like this to print out the corresponding name:

[[ o.create_uid.name ]]

Sometimes you might want to print out something only if a certain condition is fullfilled. You can construct it
with the pyhton logical operators "not", "and" and "or". Because every object in python has a logical value
(TRUE or FALSE) you can construct something like this:

[[ (o.prop=='draft') and 'YES' or 'NO' ]] print YES or NO

it works like this:

and: first value is TRUE then print out the second value. First value is FALSE print out first value.

or: first value is TRUE then print out the first value. First value is FALSE print out second value.

in this example if o.prop=='draft' -> TRUE then (o.prop=='draft') and 'YES' reads 'Yes'. Next step is 'Yes' or
'No' which leads to a printed 'YES' (because a string's logical value is TRUE). //

If o.prop=='draft' -> FALSE then it reads FALSE or 'No'. So 'No' is printed.

One can use very comlpex structures. To learn more search for some pyhton reference regarding logical
opertors.

05/02/08
149

10.7 How to add a new report


To add a new report, you need to add it to the list of reports for its module (there is one such list for each module).
Those lists are usually in files named modulename_report.xml which are located in each module directory.

modulename_report.xml format
Here is, as an example, the declaration of the reports related to invoices.

<?xml version="1.0"?>
<terp>
<data>
<delete model="ir.actions.report.xml" search="[('model','=','account.invoice')]"/>

<report string="Invoices" model="account.invoice" name="account.invoice" xml="account/


<report string="Recalls" model="res.partner" name="account.recall.receivable" auto="Fa
</data>
</terp>

Fields description
• delete attributes:

♦ model:
♦ search:
• Attributes for the report tag:

♦ id (optional):
♦ string: The string which will be displayed if there are several reports for one ressource (the user will
be presented a list with reports names).
♦ model: The name of the model where the data needed by the report is.
♦ name: The name of the report. It is used internally and should be unique.
♦ xml (optional): The path to the XML template (relative to the addons directory).
♦ xsl (optional): The path to the XSL stylesheet (relative to the addons directory).
♦ auto (optional): Whether the report should be automatically instanciated. This is should be set to False
for handcoded reports. Default value: True.
♦ replace (optional): Whether or not the report should override all existing reports for this model.
Default value: False.
♦ menu (optional): Whether or not to link the report with the print button. Default value: True.

__terp__.py
If the report you created is the first one of its module, you probably had to create the modulename_report.xml
file yourself. In that case, it should be added to the update_xml field of the __terp__.py file of the module.

Here is, for example, the __terp__.py file for the account module.

{
"name" : "Tiny ERP Accounting",
"version" : "0.1",
"depends" : ["base"],
"init_xml" : ["account_workflow.xml", "account_data.xml"],
"update_xml" : ["account_view.xml"
, "account_report.xml", "account_wizard.xml"],
}

05/02/08
150

Handcoded Reports
The steps to register a new handcoded report are roughly the same as for RML reports. In this case, the xml and xsl
attributes of the report tag are useless and can be omitted. One should not forget to add its report (python) source file
to the __init__.py of the module.

05/02/08
151

10.8 Customize Report


Customize Report Header Footer
default custome report header and footer is applied from file
“tiny-server/bin/addons/custom/corporate_rml_header.rml”

You can change the content of the file to change the effect of the report header and the footer

Change line/text Color

• <fill color="darkblue"/> - Change the Text Color


• <stroke color="darkblue"/> - Change the Line Color
Change the color according to the requirements after applying the color, you drawString that will take the
color automatically

Add / Change Image

• <image x="16.8cm" y="27.8cm" file="image.png" height="50.0" width="236.0"/>

place image file in bin folder of the tiny server


You can change the height and width of the image accordingly

Add String to Report

• <drawString x="1cm" y="27.8cm">String </ drawString>

You can set the x and y coordinate for setting the location of the string

Setting Font

• <setFont name="Helvetica" size="10"/>

Using the tag you can set the font size and name

05/02/08
152

11 Wizards
Wizards describe interaction sequences between the client and the server.

05/02/08
153

11.1 Introduction
Here is, as an example, a typical process for a wizard:

1. A window is sent to the client (a form to be completed)


2. The client sends back the data from the fields which where filled in
The server gets the result, usually execute a function and possibly sends another window/form to the client

Here is a screenshot of the wizard used to reconcile transactions (when you click on the gear icon in an account chart):

05/02/08
154

05/02/08
155

11.2 Principles
A wizard is a succession of steps. A step is composed of several actions;

1. send a form to the client and some buttons


2. get the form result and the button pressed from the client
3. execute some actions
4. send a new action to the client (form, print, ...)

To define a wizard, you have to create a class inheriting from wizard.interface and instantiate it. Each wizard must
have a unique name, which can be chosen arbitrarily except for the fact it has to start with the module name (for
example: account.move.line.reconcile). The wizard must define a dictionary named states which defines all its steps.

Here is an example of such a class:

class wiz_reconcile(wizard.interface):
states = {
'init': {
'actions': [_trans_rec_get],
'result': {'type': 'form', 'arch':_transaction_form, 'fields':_transaction_fields, 'stat
},
'reconcile': {
'actions': [_trans_rec_reconcile],
'result': {'type': 'state', 'state':'end'}
}
}
wiz_reconcile('account.move.line.reconcile');

The 'states' dictionary define all the states of the wizard. In this example; 'init' and 'reconcile'. There is another state
which is named end which is implicit.

A wizard always starts in the init state and ends in the end state.

A state define two things:

1. a list of actions
2. a result

The list of actions


Each step/state of a wizard defines a list of actions which are executed when the wizard enters the state. This list can
be empty.

The function (actions) must have the following signatures:

def _trans_rec_get(self, uid, data, res_get=False):

Where:

• self is the pointer to the wizard object


• uid is the user ID of the user which is executing the wizard
• data is a dictionary containing the following data:
♦ ids: the list of ids of resources selected when the user executed the wizard
♦ id: the id highlighted when the user executed the wizard
♦ form: a dictionary containing all the values the user completed in the preceding forms. If you change

05/02/08
156

values in this dictionary, the following forms will be pre−completed.

The result
Here are some result examples:

Result: next step


'result': {'type': 'state', 'state':'end'}

Indicate that the wizard has to continue to the next state: 'end'. If this is the 'end' state, the wizard stops.

New dialog for the client


'result': {'type': 'form', 'arch':_form, 'fields':_fields, 'state':[('reconcile','Reconcile'),('end',

The type=form indicate that this step is a dialog to the client. The dialog is composed of:

1. a form : with fields description and a form description


2. some buttons : on wich the user press after completing the form

The form description (arch) is like in the views objects. Here is an example of form:

_form = '''<?xml version="1.0"?>


<form title="Reconciliation">
<separator string="Reconciliation transactions" colspan="4"/>
<field name="trans_nbr"/>
<newline/>
<field name="credit"/>
<field name="debit"/>
<separator string="Write−Off" colspan="4"/>
<field name="writeoff"/>
<newline/>
<field name="writeoff_acc_id" colspan="3"/>
</form>'''

The fields description is similar to the fields described in the python ORM objects. Example:

_transaction_fields = {
'trans_nbr': {'string':'# of Transaction', 'type':'integer', 'readonly':True},
'credit': {'string':'Credit amount', 'type':'float', 'readonly':True},
'debit': {'string':'Debit amount', 'type':'float', 'readonly':True},
'writeoff': {'string':'Write−Off amount', 'type':'float', 'readonly':True},
'writeoff_acc_id': {'string':'Write−Off account', 'type':'many2one', 'relation':'account.account'
}

Each step/state of a wizard can have several buttons. Those are located on the bottom right of the dialog box. The list
of buttons for each step of the wizard is declared in the state key of its result dictionary.

For example: 'state':[('reconcile','Reconcile'),('end','Cancel')]

1. the next step name (determine which state will be next)


2. the button string (to display for the client)

Here is a screen−shot of this form:

05/02/08
157

Result: call a method to determine which state is next


def _check_refund(self, uid, datas):
return datas['form']['refund_id'] and 'wait_invoice' or 'end'

...
'result': {'type':'choice', 'next_state':_check_refund}

Result: print a report


def _get_invoice_id(self, uid, datas):
return {'ids': [...]}

...
'actions': [_get_invoice_id],
'result': {'type':'print', 'report':'account.invoice', 'get_id_from_action':True, 'state':'check_refund'}

05/02/08
158

11.3 How to add a new wizard


To create a new wizard, you must:

• create the wizard definition in a .py file


♦ wizards are usually defined in the wizard subdirectory of their module as in
server/bin/addons/module_name/wizard/your_wizard_name.py
• add your wizard to the list of import statements in the __init__.py file of your module's wizard subdirectory.
• declare your wizard in the database

The declaration is needed to map the wizard with a key of the client; when to launch which client. To declare a new
wizard, you need to add it to the module_name_wizard.xml file, which contains all the wizard declarations for the
module. If the that file does not exist, you need to create it first.

Here is an example of the accound_wizard.xml file;

<?xml version="1.0"?>
<terp>
<data>
<delete model="ir.actions.wizard" search="[('wiz_name','like','account.')]"/>

<wizard string="Reconcile Transactions" model="account.move.line" name="account.move.line.rec


<wizard string="Verify Transac steptions" model="account.move.line" name="account.move.line.c
<wizard string="Verify Transactions" model="account.move.line" name="account.move.line.check
<wizard string="Print Journal" model="account.account" name="account.journal"/>
<wizard string="Split Invoice" model="account.invoice" name="account.invoice.split"/>
<wizard string="Refund Invoice" model="account.invoice" name="account.invoice.refund"/>
</data>
</terp>

Attributes for the wizard tag:


• id (optional):
• string: The string which will be displayed if there are several reports for one ressource (the user will be
presented a list with reports names).
• model: The name of the model where the data needed by the report is.
• name: The name of the report. It is used internally and should be unique.
• replace (optional): Whether or not the wizard should override all existing wizards for this model. Default
value: False.
• menu (optional): Whether or not to link the wizard with the 'gears' button. Default value: True.
• keyword (optional): Bind the wizard to another action (print icon, gear icon, ...). Possible values for the
keyword attribute are:
♦ client_print_multi: the print icon in a form
♦ client_action_multi: the 'gears' icon in a form
♦ tree_but_action: the 'gears' icon in a tree view (with the shortcuts on the left)
♦ tree_but_open: the double click on a branch of a tree (with the shortcuts on the left). For example,
this is used, to bind wizards in the menu.

__terp__.py
If the wizard you created is the first one of its module, you probably had to create the modulename_wizard.xml
file yourself. In that case, it should be added to the update_xml field of the __terp__.py file of the module.

Here is, for example, the __terp__.py file for the account module.

05/02/08
159

{
"name" : "Tiny ERP Accounting",
"version" : "0.1",
"depends" : ["base"],
"init_xml" : ["account_workflow.xml", "account_data.xml"],
"update_xml" : ["account_view.xml", "account_report.xml"
, "account_wizard.xml"],
}

05/02/08
160

12 Events

12.1 Linking events to action


The available type of events are:
• client_print_multi (print from a list or form)
• client_action_multi (action from a list or form)
• tree_but_open (double click on the item of a tree, like the menu)
• tree_but_action (action on the items of a tree)

To map an events to an action:


<record model="ir.values" id="ir_open_journal_period">
<field name="key2">tree_but_open</field>
<field name="model">account.journal.period</field>
<field name="name">Open Journal</field>
<field name="value"
eval="'ir.actions.wizard,%d'%action_move_journal_line_form_select"/>
<field name="object" eval="True"/>
</record>

If you double click on a journal/period (object: account.journal.period), this will open the selected wizard.
(id="action_move_journal_line_form_select").

You can use a res_id field to allow this action only if the user click on a specific object.

<record model="ir.values" id="ir_open_journal_period"> <field


<field name="key2">tree_but_open</field>
<field name="model">account.journal.period</field>
<field name="name">Open Journal</field>
<field name="value"
eval="'ir.actions.wizard,%d'%action_move_journal_line_form_select"/>
<field name="res_id" eval="3"/>
<field name="object" eval="True"/>
</record>

The action will be triggered if the user clicks on the account.journal.period n°3.

When you declare wizard, report or menus, the ir.values creation is automatically made with these tags:
• <wizard... />
• <menuitem... />
• <report... />

So you usually do not need to add the mapping by yourself.

05/02/08
161

13 Information Repository (deprecated)


The information repository is a semantics tree in which the datas that are not the ressources are stocked. We find in
this structure:

• the values by default


• the conditional values;
♦ the state depends on the zip code,
♦ the payment method depends of the partner, ...
• the reactions to the events client;
♦ click on the invoice menu,
♦ print an invoice,
♦ action on a partner, ...

The IR has 3 methods;

• add a value in the tree


• delete a value in the tree
• obtain all the values of a selected sheet

05/02/08
162

13.1 Introduction
The Information Repository (IR) is a datas structure where you can stock some information: the values by default, the
preferences per ressource (meta) and the actions todo when certain buttons are pushed down.

The values by default


The preferences per ressource (meta)
the buttons actions
structure in tree (key, key2, models, ..., value, ...)

id | key | key2 | models | name | value | isobject | replace | meta |

1 | meta | lang |res.users |.. | | | | |


3 | action | tree_but_open | | | | | | |
20 | action | client_print_multi | | | | | | |
24 | action | client_action_multi | | | | | | |
30 | meta | res.partner | | | | | | |
36 | action | tree_but_action | | | | | | |
78 | meta | product.product | | | | | | |

Fix me! Make comment on replace and meta field

Setting the IR in xml files (version 3.1.1)


• for buttons

<ir_set>
<field name="key" eval="'action'"/>
<field name="key2" eval="'tree_but_open'"/>
<field name="models" eval="['account.account']"/>
<field name="name">Open All Move Lines</field>
<field name="value" eval="'ir.actions.act_window,%d'%a"/>
<field name="isobject" eval="True"/>
<field name="replace" eval="True"/>
</ir_set>


<ir_set>
<field name="key" eval="'action'"/>
<field name="key2" eval="'client_action_multi'"/>
<field name="models" eval="['stock.tracking']"/>
<field name="name">Tracability: Article composition</field>
<field name="value" eval="'ir.actions.act_window,'+str(action2)"/>
<field name="isobject" eval="True"/>
<field name="replace" eval="False"/>
</ir_set>

• for raports

<ir_set>
<field name="key" eval="'action'"/>

05/02/08
163

<field name="key2" eval="'client_print_multi'"/>


<field name="models" eval="['product.product']"/>
<field name="name">Product Cost Structure</field>
<field name="value" eval="'ir.actions.wizard,%d'%wizard_price"/>
<field name="isobject" eval="True"/>
<field name="replace" eval="True"/>
</ir_set>

• for meta

♦ '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
<field name="replace" eval="True"/>
</ir_set>

◊ 'account.receivable'

<ir_set>
<field name="key" eval="'meta'"/>
<field name="key2" eval="'account.receivable'"/>
<field name="models" eval="['res.partner']"/>
<field name="name">account.receivable</field>
<field name="value" eval="a_recv"/>
<field name="meta" eval="{'type':'many2one', 'string':'Account Receivable', 'relation'
</ir_set>

◊ 'account.payable'

<ir_set>
<field name="key" eval="'meta'"/>
<field name="key2" eval="'account.payable'"/>
<field name="models" eval="['res.partner']"/>
<field name="name">account.payable</field>
<field name="value" eval="a_pay"/>
<field name="meta" eval="{'type':'many2one', 'string':'Account Payable', 'relation':'a
</ir_set>

05/02/08
164

13.2 Structure

05/02/08
165

13.3 Access Methods


• def ir_set(cr, uid, keys, args, name, value, replace=True, isobject=False, meta=None)

Description of the fields


• keys:
• args:
• name:
• value:
• isobject:
• replace: whether or not the action described should override an existing action or be appended to the list of
actions.
• def ir_get(cr, uid, keys, args=[], meta=False, context={}):

res = ir.ir_get(cr, uid, [('default', self._name), ('field', False)], [('user_id',str(uid))])


account_payable = ir.ir_get(cr, uid, [('meta','res.partner'), ('name','account.payable')], opt)[0][2]

• def ir_del(cr, uid, id):

05/02/08
166

14 MRPModule
Stock Concepts
Reservation
Reservation for a quantity of one products (wich may be several lot_lines with the same products).

__States__:

• draft : not take into account


• waiting : not take into account, waiting that an operation wake up this operation
• confirmed : lots lines are reserved
• done : unused, finnished
• cancel : unused, canceled

The key point for make to order operations. Reservations may be linked: once the operation is done, the lots are
automatically linked to another reservations for another operations. (for eg, a waiting reservation)

Once a reservation is done it:

• Finnish the associated picking list if completed


• Allocate lot line to linked reservation and wake up this waiting reservation

Once some lots are reserved they can not be used for another operation.

Reservation may be placed on a location and assigned later to the right lots.

Picking list:
Takes some reservations. Once that reservations are all confirmed, the picking list can be processed. When you
process the picking list, it takes all reserved lots lines and create a new lot with these reserved products.

You can create partial parcels with the wizard.

MRP Concepts
Procurement
The form that allows the program to produce, buy or reserve products. (=procure products). Then these products are
assigned to a reservation.

Procurement may have 2 methods:

• make to stock : take products on the unreserved stock


• make to order : buy or produce these products and after assign to this procurement

Have a look at the workflow:

05/02/08
167

Procurements may wait in the states 'make to stock' or 'make to order'. Then the 'calcul des besoins net' processor will
run all procurements one by one ordered by priorities.

Attach:mrp_reservation.png

05/02/08
168

14.1 Workcenters
capacity_hour
Capacity per hour. default: 1.0. Eg: If 5 concurrent operations can be done at one time: capacity = 5 (because
5 employees for example).
unit_per_cycle
Units produce in one cycle. default 1.0.
time_efficiency
Time efficiency for production. Factor default to 1.0
costs_hour
Costs for one hour of production in this workcenter.
costs_cycle
Fixed cost for each cycle of production,
cost_setup
Fixed cost for the setup of the workcenter

05/02/08
169

14.2 Bill of Materials


Main BOM Infos
active
BOM active or not
workcenter_id
Workcenter to produce to transformations operations
product_id
Product generated with this BOM
product_qty
Qty of this product produced with all product lines bellow

BOM Lines
Materials used to compose a product_qty of these products.

05/02/08
170

15 Modules
The structure of Tiny ERP are divided into modules. The available modules are;

• account
• auction
• base
• crm
• marketing
• product
• purchase
• sale
• stock
• project
• hr
• subscription
• Audittrail

The different modules are situated in the repertory: server/addons.

05/02/08
171

15.1 Writing a new module


All the modules are situated in the repertory server/addons. You can delete or not a module durong the installation.

The following steps are necessary to create a new module;

• to create a repertory in the server/addons


• to create a module description file: __terp__.py
• to create the Python file containing the objects
• to create .xml files that download the datas (views, workflows, ...)
• eventually to create reports and wizards.

The __terp__.py file


In the module repertory created, you must add a __terp__.py file. This file, with Python format, is responsible to
determinate the files that will be executed at the starting of the server and also the dependences of this module.

This file must contain a Python dictionnary with the following values:

name
The name of (english) the module
version
The version of the developped module
depends
List of modules which depends this module. The basic module must be in the dependences because certain
necessary datas for the views, reports, ... are in the basic module.
init_xml
List of .xml files to download when the server is launched with the "−−init=module" argument. The way is
relative to the repertory of the concerned module.
update_xml
List of .xml files to download when the server is launched with the "−−update=module" launched. The way is
relative to the concerned module repertory.
active
True or False. Determinate if the module is active or not.

Example
Here is an un example of __terp__.py file for the Account module.

{
"name" : "Tiny TERP Accounting",
"version" : "1.0",
"depends" : ["base"],
"init_xml" : [ "account_workflow.xml", "account_data.xml", "account_demo.xml"],
"update_xml" : [ "account_view.xml", "account_report.xml", "account_wizard.xml"],
}

The files that must be placed in init_xml, are the ones that concern the workflow definition, the datas to download at
the installation of the softwaredu logiciel and the datas for the demonstrations.

The files in "update_xml" concern: the views, the reports and the wizards.

05/02/08
172

The __init__.py file


The __init__.py file is, like all the Python module,executed at the start of the program. It needs to import the Python
files for being loaded.

So, if you create a "module.py" file, containing the description of your objects, you have to write one line in
__init__.py; import module

05/02/08
173

15.2 Modules guidelines

Guidelines for the name of a module:

• You can use the '_' character as a separator


• If your module is a submodule of another module, you can name it: module_submodule
• Do not use uppercase, special characters or '.' in the name of a module

Reports and boards:

• Extra statistical reports on a specific module should go in modules of the form: 'report_mymodule'
• Boards module must go in modules like 'board_mymodule'
• Reports are structured by module: account, sandwich, sale, purchase
• Board are structured by poste: executive, accountant, sales manager, project manager, ...
• A board might depend on different report modules. Some examples:
º Executive (report_sale,report_account,report_purchase)
º Developper (report_project, report_sandwich, report_crm for support).

05/02/08
174

15.3 Network management


For example, let's develop quickly a module that could manage the materiel installed to the customers. The
specifications are the next ones;

• a management of different materiels installed; computers, screens, switch, ...


• a management of the relations between the components;
♦ computers running on a switch
♦ the print running on a computer
• a mangement of configurations; users, passwords on every computer
• differents components must be attached to the clients

Few screenshots of our network management.

05/02/08
175

16 Webservices
1. WebservicesIntro
2. WebservicesXMLRPC
3. WebserviceExemplePHP
4. WebserviceExemplePython

05/02/08
176

16.1 WebservicesIntro

How to load data ?


• Postgresql
♦ Simple, standard
♦ Does not respect the WORKFLOW !!!
• XML files (avec –update=)
• XML−RPC
♦ Script, same as website interface

How to backup/restore a Postgresql database?


backup
pg_dump terp >terp.sql
restore
createdb terp −−encoding=unicode

psql terp <terp.sql

The objects methods


• create({'field':'value'})
♦ return ID created
• search([('arg1','=','value1')...], offset=0, limit=1000)
♦ return [IDS] found
• read([IDS], ['field1','field2',...])
♦ return [{'id':1, 'field1':..., 'field2':..., ...}, ...]
• write([IDS], {'field1':'value1','field2':3})
♦ return True
• unlink([IDS])
♦ return True

05/02/08
177

16.2 WebservicesXMLRPC
• XML−RPC
♦ standard: http://xmlrpc.org
♦ RPC Over HTTP
♦ Function Parameters & Result encoded in XML
• Principe;
♦ appels aux méthodes des objets;
◊ read, write
◊ create
◊ unlink (=delete)

XML−RPC is known as a web service. Web services are a set of tools that let one build distributed applications on top
of existing web infrastructures. These applications use the Web as a kind of "transport layer" but don't offer a direct
human interface via the browser.[1] Extensible Markup Language (XML) provides a vocabulary for describing
Remote Procedure Calls (RPC), which is then transmitted between computers using the HyperText Transfer Protocol
(HTTP). Effectively, RPC gives developers a mechanism for defining interfaces that can be called over a network.
These interfaces can be as simple as a single function call or as complex as a large API.

XML−RPC therefore allows two or more computers running different operating systems and programs written in
different languages to share processing. For example, a Java application could talk with a Perl program, which in turn
talks with Python application that talks with ASP, and so on. System integrators often build custom connections
between different systems, creating their own formats and protocols to make communications possible, but one can
often end up with a large number of poorly documented single−use protocols. The RPC approach spares programmers
the trouble of having to learn about underlying protocols, networking, and various implementation details.

XML−RPC can be used with Python, Java, Perl, PHP, C, C++, Ruby, Microsoft’s .NET and many other programming
languages. Implementations are widely available for platforms such as Unix, Linux, Windows and the Macintosh.

An XML−RPC call is conducted between two parties: the client (the calling process) and the server (the called
process). A server is made available at a particular URL (such as http://example.org:8080/rpcserv/).

The above text just touches the surface of XML−RPC. I recommend O'Reilly's "Programming Web Service with
XML−RPC" for further reading. One may also wish to review the following links:

XML−RPC Home Page


XML−RPC for C and C++
The Apache XML−RPC Project
Expat: The XML Parser

05/02/08
178

16.3 An example in PHP


Here is an example on how to insert a new partner using PHP. This example makes use the phpxmlrpc library,
available on sourceforge.

<?
include('xmlrpc.inc');

$arrayVal = array(
'name'=>new xmlrpcval('Fabien Pinckaers', "string") ,
'vat'=>new xmlrpcval('BE477472701' , "string")
);

$client = new xmlrpc_client("/xmlrpc/object", "localhost", 8069);

$msg = new xmlrpcmsg('execute');


$msg−>addParam(new xmlrpcval("3", "int"));
$msg−>addParam(new xmlrpcval("demo", "string"));
$msg−>addParam(new xmlrpcval("res.partner", "string"));
$msg−>addParam(new xmlrpcval("create", "string"));
$msg−>addParam(new xmlrpcval($arrayVal, "struct"));

$resp = $client−>send($msg);

$val = $resp−>value();
$id = $val−>scalarval();
echo "Partner $id created!";
?>

05/02/08
179

16.4 An example in Python


Example of creation of a partner and his address.

import xmlrpclib

sock = xmlrpclib.ServerProxy('http://localhost:8069/xmlrpc/object')
uid = 1
pwd = 'demo'

partner = {
'title': 'Monsieur',
'name': 'Fabien Pinckaers',
'lang': 'fr',
'active': True,
}

partner_id = sock.execute(uid, pwd, 'res.partner', 'create', partner)

address = {
'partner_id': partner_id,
'type': 'default',
'street': 'Rue du vieux chateau, 21',
'zip': '1457',
'city': 'Walhain',
'phone': '(+32)10.68.94.39',
'fax': '(+32)10.68.94.39',
}

sock.execute(uid, pwd, 'res.partner.address', 'create', address)

05/02/08
180

17.1 Load Balancing between servers

Load Balancing
There is two ways to do load−balancing;

• on the Tiny ERP server


• on the relationnal database

Tiny ERP Server


You can use any http proxy to make some load−balancing on Tiny ERP. All servers have to use the same database.
For example, with balance (http://www.inlab.de/balance.html):

./balance 8070 serveur_1:8069 serveur_2:8069

Then, if you connect with the Tiny ERP client on the server you started balance, on port 8070, requests are
load−balanced between serveur_1 and serveur_2. If can start different balance server if you need redundancy for
QOS.

Postgresql
Have a look at postgresql replication system. Each Tiny ERP server uses one and only one postgresl database. You
need to configure each server to use one replicated database.

05/02/08
181

18. Multi−Company

Security in the client side


The multi-company in Tiny ERP is very flexible.

To edit companies, go to the menu;


Administration > Users > Company Structure > Define Company

Companies are structured in trees. An example;


• Tiny Group
º Tiny Company 1
■ Tiny Comp 1.1

º Tiny Company 2

Someone in the 'Tiny Company 1' will get an access of ressources that are to these companies; 'Tiny
Company 1', 'Tiny Comp 1.1'. He will not see the ressources of 'Tiny Group' or 'Tiny Company 2'.

Some fields, records or reports have different values depending on the company you work in. Some example;
• The credit / debit in the account chart is for your own company.
• The report header my be different for different companies
• Some records will available to some companies only.

Each users must be attached to a company.

Ressources that doesn't have a company are visible for all companies.

On the server side

When you want to filters records automatically according to your company context, just add a 'company_id'
fields in the object that have to be filtered. If the company_id field is null for a record, this record will be
available for every body.

Example with the account chart

By default, all companies write account move line in the same account charts. Account.move.line may have a
compay_id fields, wich id defined like this:
'company_id': fields.many2one('res.company', 'Company')
and with a default value:
'company_id': lambda self,cr,uid,context: context.get('company', False)

05/02/08
182

If you browse the account chart, you will see different credit/debit according to your company. This is
because the sum of your visible move.line are different.

Different charts for different companies

If you plan to have differents account charts for differents companies, you must add a company_id field in the
account.account object. So, companies may share most of the account charts and have specific accounts for
their own use.

Different credit/debit account for your partners

If you want to use a different credit account for your partners for different companies, add a company_id field
in your 'ir.values' object. So, all preferences of your partners will depend of the company.

05/02/08
183

19. Internationalization
Module creation
To customize tinyerp to suit your local needs you must first create a directory where all your local
configuration will take place. So you should at first create this directory:

cd $TINYERP_DIRECTORY
mkdir addons/l10n_be

You should change the last two letters of the directory name (here the be of l10n_be) by the ISO Code for
your country.

It is in this directory that all your localisation works should go.

The __terp__.py file


Here is an example __terp__.py file that you should add in the addons/l10n_be directory you just
created.

{
"name" : "Localisation for Belgium",
"version" : "1.0",
"author" : "Tiny",
"category" : "Localisation/Europe",
"depends" : ["base", "account"],
"init_xml" : [],
"demo_xml" : ['account_demo.xml'],
"update_xml" : ['account_pcmn_belgium.xml'],
"active": False
}

Of course you should also change all the reference to Belgium that are found in the example above.

See WritingANewModule

In the demo_xml field there is a list of file that are read on the first startup of tinyerp and that insert some data
into the database. More interesting is the update_xml field. This list defines a number of files that are
reloaded each time tinyerp is invoked with the --update switch positioned on one of the dependency of the
current module (or on this module of course).

The account creation


At first you should take a look at the file addons/account/data/account_minimal.xml, this file is
sourced on every update of the account module. It defines the minimal account tree needed by tinyerp to
operate at its best.

05/02/08
184

Above everything there is one thing to take into account when you are writing your own account tree. You
must keep the ids used in account_minimal.xml in your own version of the account tree. Another
important thing is that every acount defined in account_minimal should be defined in your own account
tree.

That is, in your xml file defining your country account tree the record defining the Main receivable account
should look like

<record model="account.account" id="account.a_recv">


<field name="name">Main Receivable</field>
<field name="code">40</field>
<field name="type">receivable</field>
<field name="currency_id" ref="account.EUR"/>
<field name="parent_id" eval="[(6,0,[a5])]"/>
</record>

In this example, you can see that the id associated to the account that is created is account.a_recv which
match the id defined for the Main receivable account in account_minimal.xml.

Account types

There is nine account types: receivable, payable, view, income, expense, tax, cash, asset and equity.

The only particular type is the view type wich is used to structure the account tree.

How to make links

As yoou might have noticed an account can have more than one father. That's why we are using a special
syntax to create links from the account to its father:

<field name="parent_id" eval="[(6,0,[a5])]"/>

This line means that the current node should be linked to the node identified by the id a5.

The constraints on the account tree


There is three constraints pertaining to the account tree creation:

• You must not use an account type of view as a leaf,


• The root of the account tree must use as its code,
• and there must be at least one account type in the tree.

Taxes
If your not living in some financial paradise, you have to define some taxes. Here are the fields that you must
use to define the taxes.

name
This is the name of the tax, it will appear on the invoice.

05/02/08
185

type
This is the type of the tax, it can be percent, fixed, none or code. Percent apply this percentage to the
total amount, fixed add a fixed amount to the total amount, none means that the tax is not applied
(but its
child might be), code means that the amount is computed using python code.
amount
This is the amount used by the tax. eg.: a tax with an amount of 21 and a type of percent will add 21%
to the total amount of the invoice.
account_collected_id
This is the account that will collect the amount this peculiar tax
account_paid_id
This is the account that is used to pay the amount of this peculiar tax

Example
You should have a look at the "addons/l10n_belgium" directory for a detailed example.

05/02/08

You might also like