Odoo is mostly extended internally via modules, but much of its features and all of its data is also available from the outside for external analysis or integration with various tools. Part of the Model Reference API is easily available over XML-RPC and accessible from a variety of languages.
import xmlrpclib
info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start()
url, db, username, password = \
info['host'], info['database'], info['user'], info['password']
common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
uid = common.authenticate(db, username, password, {})
models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
require "xmlrpc/client"
info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start')
url, db, username, password = \
info['host'], info['database'], info['user'], info['password']
common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
uid = common.call('authenticate', db, username, password, {})
models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy
require_once('ripcord.php');
$info = ripcord::client('https://demo.odoo.com/start')->start();
list($url, $db, $username, $password) =
array($info['host'], $info['database'], $info['user'], $info['password']);
$common = ripcord::client("$url/xmlrpc/2/common");
$uid = $common->authenticate($db, $username, $password, array());
$models = ripcord::client("$url/xmlrpc/2/object");
final XmlRpcClient client = new XmlRpcClient();
final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl();
start_config.setServerURL(new URL("https://demo.odoo.com/start"));
final Map<String, String> info = (Map<String, String>)client.execute(
start_config, "start", emptyList());
final String url = info.get("host"),
db = info.get("database"),
username = info.get("user"),
password = info.get("password");
final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url)));
int uid = (int)client.execute(
common_config, "authenticate", Arrays.asList(
db, username, password, emptyMap()));
final XmlRpcClient models = new XmlRpcClient() {{
setConfig(new XmlRpcClientConfigImpl() {{
setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
}});
}};
If you already have an Odoo server installed, you can just use its parameters
url = <insert server URL>
db = <insert database name>
username = 'admin'
password = <insert password for your admin user (default: admin)>
url = <insert server URL>
db = <insert database name>
username = "admin"
password = <insert password for your admin user (default: admin)>
$url = <insert server URL>;
$db = <insert database name>;
$username = "admin";
$password = <insert password for your admin user (default: admin)>;
final String url = <insert server URL>,
db = <insert database name>,
username = "admin",
password = <insert password for your admin user (default: admin)>;
To make exploration simpler, you can also ask https://demo.odoo.com for a test database:
import xmlrpclib
info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start()
url, db, username, password = \
info['host'], info['database'], info['user'], info['password']
require "xmlrpc/client"
info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start')
url, db, username, password = \
info['host'], info['database'], info['user'], info['password']
require_once('ripcord.php');
$info = ripcord::client('https://demo.odoo.com/start')->start();
list($url, $db, $username, $password) =
array($info['host'], $info['database'], $info['user'], $info['password']);
final XmlRpcClient client = new XmlRpcClient();
final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl();
start_config.setServerURL(new URL("https://demo.odoo.com/start"));
final Map<String, String> info = (Map<String, String>)client.execute(
start_config, "start", emptyList());
final String url = info.get("host"),
db = info.get("database"),
username = info.get("user"),
password = info.get("password");
Note
These examples use the Ripcord library, which provides a simple XML-RPC API. Ripcord requires that XML-RPC support be enabled in your PHP installation.
Since calls are performed over HTTPS, it also requires that the OpenSSL extension be enabled.
Note
These examples use the Apache XML-RPC library
The examples do not include imports as these imports couldn’t be pasted in the code.
Odoo requires users of the API to be authenticated before being able to query much data.
The xmlrpc/2/common endpoint provides meta-calls which don’t require authentication, such as the authentication itself or fetching version information. To verify if the connection information is correct before trying to authenticate, the simplest call is to ask for the server’s version. The authentication itself is done through the authenticate function and returns a user identifier (uid) used in authenticated calls instead of the login.
common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
common.version()
common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
common.call('version')
$common = ripcord::client("$url/xmlrpc/2/common");
$common->version();
final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
common_config.setServerURL(
new URL(String.format("%s/xmlrpc/2/common", url)));
client.execute(common_config, "version", emptyList());
{
"server_version": "8.0",
"server_version_info": [8, 0, 0, "final", 0],
"server_serie": "8.0",
"protocol_version": 1,
}
uid = common.authenticate(db, username, password, {})
uid = common.call('authenticate', db, username, password, {})
$uid = $common->authenticate($db, $username, $password, array());
int uid = (int)client.execute(
common_config, "authenticate", asList(
db, username, password, emptyMap()));
The second endpoint is xmlrpc/2/object, is used to call methods of odoo models via the execute_kw RPC function.
Each call to execute_kw takes the following parameters:
For instance to see if we can read the res.partner model we can call check_access_rights with operation passed by position and raise_exception passed by keyword (in order to get a true/false result rather than true/error):
models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
models.execute_kw(db, uid, password,
'res.partner', 'check_access_rights',
['read'], {'raise_exception': False})
models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy
models.execute_kw(db, uid, password,
'res.partner', 'check_access_rights',
['read'], {raise_exception: false})
$models = ripcord::client("$url/xmlrpc/2/object");
$models->execute_kw($db, $uid, $password,
'res.partner', 'check_access_rights',
array('read'), array('raise_exception' => false));
final XmlRpcClient models = new XmlRpcClient() {{
setConfig(new XmlRpcClientConfigImpl() {{
setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
}});
}};
models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "check_access_rights",
asList("read"),
new HashMap() {{ put("raise_exception", false); }}
));
true
Records can be listed and filtered via search().
search() takes a mandatory domain filter (possibly empty), and returns the database identifiers of all records matching the filter. To list customer companies for instance:
models.execute_kw(db, uid, password,
'res.partner', 'search',
[[['is_company', '=', True], ['customer', '=', True]]])
models.execute_kw(db, uid, password,
'res.partner', 'search',
[[['is_company', '=', true], ['customer', '=', true]]])
$models->execute_kw($db, $uid, $password,
'res.partner', 'search', array(
array(array('is_company', '=', true),
array('customer', '=', true))));
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search",
asList(asList(
asList("is_company", "=", true),
asList("customer", "=", true)))
)));
[7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74]
By default a research will return the ids of all records matching the condition, which may be a huge number. offset and limit parameters are available to only retrieve a subset of all matched records.
models.execute_kw(db, uid, password,
'res.partner', 'search',
[[['is_company', '=', True], ['customer', '=', True]]],
{'offset': 10, 'limit': 5})
models.execute_kw(db, uid, password,
'res.partner', 'search',
[[['is_company', '=', true], ['customer', '=', true]]],
{offset: 10, limit: 5})
$models->execute_kw($db, $uid, $password,
'res.partner', 'search',
array(array(array('is_company', '=', true),
array('customer', '=', true))),
array('offset'=>10, 'limit'=>5));
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search",
asList(asList(
asList("is_company", "=", true),
asList("customer", "=", true))),
new HashMap() {{ put("offset", 10); put("limit", 5); }}
)));
[13, 20, 30, 22, 29]
Rather than retrieve a possibly gigantic list of records and count them afterwards, search_count() can be used to retrieve only the number of records matching the query. It takes the same domain filter as search() and no other parameter.
models.execute_kw(db, uid, password,
'res.partner', 'search_count',
[[['is_company', '=', True], ['customer', '=', True]]])
models.execute_kw(db, uid, password,
'res.partner', 'search_count',
[[['is_company', '=', true], ['customer', '=', true]]])
$models->execute_kw($db, $uid, $password,
'res.partner', 'search_count',
array(array(array('is_company', '=', true),
array('customer', '=', true))));
(Integer)models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search_count",
asList(asList(
asList("is_company", "=", true),
asList("customer", "=", true)))
));
19
Warning
calling search then search_count (or the other way around) may not yield coherent results if other users are using the server: stored data could have changed between the calls
Record data is accessible via the read() method, which takes a list of ids (as returned by search()) and optionally a list of fields to fetch. By default, it will fetch all the fields the current user can read, which tends to be a huge amount.
ids = models.execute_kw(db, uid, password,
'res.partner', 'search',
[[['is_company', '=', True], ['customer', '=', True]]],
{'limit': 1})
[record] = models.execute_kw(db, uid, password,
'res.partner', 'read', [ids])
# count the number of fields fetched by default
len(record)
ids = models.execute_kw(db, uid, password,
'res.partner', 'search',
[[['is_company', '=', true], ['customer', '=', true]]],
{limit: 1})
record = models.execute_kw(db, uid, password,
'res.partner', 'read', [ids]).first
# count the number of fields fetched by default
record.length
$ids = $models->execute_kw($db, $uid, $password,
'res.partner', 'search',
array(array(array('is_company', '=', true),
array('customer', '=', true))),
array('limit'=>1));
$records = $models->execute_kw($db, $uid, $password,
'res.partner', 'read', array($ids));
// count the number of fields fetched by default
count($records[0]);
final List ids = asList((Object[])models.execute(
"execute_kw", asList(
db, uid, password,
"res.partner", "search",
asList(asList(
asList("is_company", "=", true),
asList("customer", "=", true))),
new HashMap() {{ put("limit", 1); }})));
final Map record = (Map)((Object[])models.execute(
"execute_kw", asList(
db, uid, password,
"res.partner", "read",
asList(ids)
)
))[0];
// count the number of fields fetched by default
record.size();
121
Conversedly, picking only three fields deemed interesting.
models.execute_kw(db, uid, password,
'res.partner', 'read',
[ids], {'fields': ['name', 'country_id', 'comment']})
models.execute_kw(db, uid, password,
'res.partner', 'read',
[ids], {fields: %w(name country_id comment)})
$models->execute_kw($db, $uid, $password,
'res.partner', 'read',
array($ids),
array('fields'=>array('name', 'country_id', 'comment')));
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "read",
asList(ids),
new HashMap() {{
put("fields", asList("name", "country_id", "comment"));
}}
)));
[{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}]
Note
even if the id field is not requested, it is always returned
fields_get() can be used to inspect a model’s fields and check which ones seem to be of interest.
Because it returns a great amount of meta-information (it is also used by client programs) it should be filtered before printing, the most interesting items for a human user are string (the field’s label), help (a help text if available) and type (to know which values to expect, or to send when updating a record):
models.execute_kw(
db, uid, password, 'res.partner', 'fields_get',
[], {'attributes': ['string', 'help', 'type']})
models.execute_kw(
db, uid, password, 'res.partner', 'fields_get',
[], {attributes: %w(string help type)})
$models->execute_kw($db, $uid, $password,
'res.partner', 'fields_get',
array(), array('attributes' => array('string', 'help', 'type')));
(Map<String, Map<String, Object>>)models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "fields_get",
emptyList(),
new HashMap() {{
put("attributes", asList("string", "help", "type"));
}}
));
{
"ean13": {
"type": "char",
"help": "BarCode",
"string": "EAN13"
},
"property_account_position_id": {
"type": "many2one",
"help": "The fiscal position will determine taxes and accounts used for the partner.",
"string": "Fiscal Position"
},
"signup_valid": {
"type": "boolean",
"help": "",
"string": "Signup Token is Valid"
},
"date_localization": {
"type": "date",
"help": "",
"string": "Geo Localization Date"
},
"ref_company_ids": {
"type": "one2many",
"help": "",
"string": "Companies that refers to partner"
},
"sale_order_count": {
"type": "integer",
"help": "",
"string": "# of Sales Order"
},
"purchase_order_count": {
"type": "integer",
"help": "",
"string": "# of Purchase Order"
},
Because that is a very common task, Odoo provides a search_read() shortcut which as its name notes is equivalent to a search() followed by a read(), but avoids having to perform two requests and keep ids around.
Its arguments are similar to search()‘s, but it can also take a list of fields (like read(), if that list is not provided it’ll fetch all fields of matched records):
models.execute_kw(db, uid, password,
'res.partner', 'search_read',
[[['is_company', '=', True], ['customer', '=', True]]],
{'fields': ['name', 'country_id', 'comment'], 'limit': 5})
models.execute_kw(db, uid, password,
'res.partner', 'search_read',
[[['is_company', '=', true], ['customer', '=', true]]],
{fields: %w(name country_id comment), limit: 5})
$models->execute_kw($db, $uid, $password,
'res.partner', 'search_read',
array(array(array('is_company', '=', true),
array('customer', '=', true))),
array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5));
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search_read",
asList(asList(
asList("is_company", "=", true),
asList("customer", "=", true))),
new HashMap() {{
put("fields", asList("name", "country_id", "comment"));
put("limit", 5);
}}
)));
[
{
"comment": false,
"country_id": [ 21, "Belgium" ],
"id": 7,
"name": "Agrolait"
},
{
"comment": false,
"country_id": [ 76, "France" ],
"id": 18,
"name": "Axelor"
},
{
"comment": false,
"country_id": [ 233, "United Kingdom" ],
"id": 12,
"name": "Bank Wealthy and sons"
},
{
"comment": false,
"country_id": [ 105, "India" ],
"id": 14,
"name": "Best Designers"
},
{
"comment": false,
"country_id": [ 76, "France" ],
"id": 17,
"name": "Camptocamp"
}
]
Records of a model are created using create(). The method will create a single record and return its database identifier.
create() takes a mapping of fields to values, used to initialize the record. For any field which has a default value and is not set through the mapping argument, the default value will be used.
id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{
'name': "New Partner",
}])
id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{
name: "New Partner",
}])
$id = $models->execute_kw($db, $uid, $password,
'res.partner', 'create',
array(array('name'=>"New Partner")));
final Integer id = (Integer)models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "create",
asList(new HashMap() {{ put("name", "New Partner"); }})
));
78
Warning
while most value types are what would be expected (integer for Integer, string for Char or Text),
Records can be updated using write(), it takes a list of records to update and a mapping of updated fields to values similar to create().
Multiple records can be updated simultanously, but they will all get the same values for the fields being set. It is not currently possible to perform “computed” updates (where the value being set depends on an existing value of a record).
models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {
'name': "Newer partner"
}])
# get record name after having changed it
models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])
models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {
name: "Newer partner"
}])
# get record name after having changed it
models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])
$models->execute_kw($db, $uid, $password, 'res.partner', 'write',
array(array($id), array('name'=>"Newer partner")));
// get record name after having changed it
$models->execute_kw($db, $uid, $password,
'res.partner', 'name_get', array(array($id)));
models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "write",
asList(
asList(id),
new HashMap() {{ put("name", "Newer Partner"); }}
)
));
// get record name after having changed it
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "name_get",
asList(asList(id))
)));
[[78, "Newer partner"]]
Records can be deleted in bulk by providing the ids of all records to remove to unlink().
models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
# check if the deleted record is still in the database
models.execute_kw(db, uid, password,
'res.partner', 'search', [[['id', '=', id]]])
models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
# check if the deleted record is still in the database
models.execute_kw(db, uid, password,
'res.partner', 'search', [[['id', '=', id]]])
$models->execute_kw($db, $uid, $password,
'res.partner', 'unlink',
array(array($id)));
// check if the deleted record is still in the database
$models->execute_kw($db, $uid, $password,
'res.partner', 'search',
array(array(array('id', '=', $id))));
models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "unlink",
asList(asList(id))));
// check if the deleted record is still in the database
asList((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search",
asList(asList(asList("id", "=", 78)))
)));
[]
While we previously used fields_get() to query a model’s and have been using an arbitrary model from the start, Odoo stores most model metadata inside a few meta-models which allow both querying the system and altering models and fields (with some limitations) on the fly over XML-RPC.
Provides informations about Odoo models themselves via its various fields
ir.model can be used to
Warning
a custom model will initially contain only the “built-in” fields available on all models:
models.execute_kw(db, uid, password, 'ir.model', 'create', [{
'name': "Custom Model",
'model': "x_custom_model",
'state': 'manual',
}])
models.execute_kw(
db, uid, password, 'x_custom_model', 'fields_get',
[], {'attributes': ['string', 'help', 'type']})
$models->execute_kw(
$db, $uid, $password,
'ir.model', 'create', array(array(
'name' => "Custom Model",
'model' => 'x_custom_model',
'state' => 'manual'
))
);
$models->execute_kw(
$db, $uid, $password,
'x_custom_model', 'fields_get',
array(),
array('attributes' => array('string', 'help', 'type'))
);
models.execute_kw(
db, uid, password,
'ir.model', 'create', [{
name: "Custom Model",
model: 'x_custom_model',
state: 'manual'
}])
fields = models.execute_kw(
db, uid, password, 'x_custom_model', 'fields_get',
[], {attributes: %w(string help type)})
models.execute(
"execute_kw", asList(
db, uid, password,
"ir.model", "create",
asList(new HashMap<String, Object>() {{
put("name", "Custom Model");
put("model", "x_custom_model");
put("state", "manual");
}})
));
final Object fields = models.execute(
"execute_kw", asList(
db, uid, password,
"x_custom_model", "fields_get",
emptyList(),
new HashMap<String, Object> () {{
put("attributes", asList(
"string",
"help",
"type"));
}}
));
{
"create_uid": {
"type": "many2one",
"string": "Created by"
},
"create_date": {
"type": "datetime",
"string": "Created on"
},
"__last_update": {
"type": "datetime",
"string": "Last Modified on"
},
"write_uid": {
"type": "many2one",
"string": "Last Updated by"
},
"write_date": {
"type": "datetime",
"string": "Last Updated on"
},
"display_name": {
"type": "char",
"string": "Display Name"
},
"id": {
"type": "integer",
"string": "Id"
}
}
Provides informations about the fields of Odoo models and allows adding custom fields without using Python code
Like custom models, only new fields created with state="manual" are activated as actual fields on the model.
Warning
computed fields can not be added via ir.model.fields, some field meta-information (defaults, onchange) can not be set either
id = models.execute_kw(db, uid, password, 'ir.model', 'create', [{
'name': "Custom Model",
'model': "x_custom",
'state': 'manual',
}])
models.execute_kw(
db, uid, password,
'ir.model.fields', 'create', [{
'model_id': id,
'name': 'x_name',
'ttype': 'char',
'state': 'manual',
'required': True,
}])
record_id = models.execute_kw(
db, uid, password,
'x_custom', 'create', [{
'x_name': "test record",
}])
models.execute_kw(db, uid, password, 'x_custom', 'read', [[record_id]])
$id = $models->execute_kw(
$db, $uid, $password,
'ir.model', 'create', array(array(
'name' => "Custom Model",
'model' => 'x_custom',
'state' => 'manual'
))
);
$models->execute_kw(
$db, $uid, $password,
'ir.model.fields', 'create', array(array(
'model_id' => $id,
'name' => 'x_name',
'ttype' => 'char',
'state' => 'manual',
'required' => true
))
);
$record_id = $models->execute_kw(
$db, $uid, $password,
'x_custom', 'create', array(array(
'x_name' => "test record"
))
);
$models->execute_kw(
$db, $uid, $password,
'x_custom', 'read',
array(array($record_id)));
id = models.execute_kw(
db, uid, password,
'ir.model', 'create', [{
name: "Custom Model",
model: "x_custom",
state: 'manual'
}])
models.execute_kw(
db, uid, password,
'ir.model.fields', 'create', [{
model_id: id,
name: "x_name",
ttype: "char",
state: "manual",
required: true
}])
record_id = models.execute_kw(
db, uid, password,
'x_custom', 'create', [{
x_name: "test record"
}])
models.execute_kw(
db, uid, password,
'x_custom', 'read', [[record_id]])
final Integer id = (Integer)models.execute(
"execute_kw", asList(
db, uid, password,
"ir.model", "create",
asList(new HashMap<String, Object>() {{
put("name", "Custom Model");
put("model", "x_custom");
put("state", "manual");
}})
));
models.execute(
"execute_kw", asList(
db, uid, password,
"ir.model.fields", "create",
asList(new HashMap<String, Object>() {{
put("model_id", id);
put("name", "x_name");
put("ttype", "char");
put("state", "manual");
put("required", true);
}})
));
final Integer record_id = (Integer)models.execute(
"execute_kw", asList(
db, uid, password,
"x_custom", "create",
asList(new HashMap<String, Object>() {{
put("x_name", "test record");
}})
));
client.execute(
"execute_kw", asList(
db, uid, password,
"x_custom", "read",
asList(asList(record_id))
));
[
{
"create_uid": [1, "Administrator"],
"x_name": "test record",
"__last_update": "2014-11-12 16:32:13",
"write_uid": [1, "Administrator"],
"write_date": "2014-11-12 16:32:13",
"create_date": "2014-11-12 16:32:13",
"id": 1,
"display_name": "test record"
}
]
Workflows can be moved along by sending them signals. Instead of using the top-level execute_kw, signals are sent using exec_workflow.
Signals are sent to a specific record, and possibly trigger a transition on the workflow instance associated with the record.
Warning
requires that the account module be installed
client = models.execute_kw(
db, uid, password,
'res.partner', 'search_read',
[[('customer', '=', True)]],
{'limit': 1, 'fields': [
'property_account_receivable_id',
'property_payment_term_id',
'property_account_position_id']
})[0]
invoice_id = models.execute_kw(
db, uid, password,
'account.invoice', 'create', [{
'partner_id': client['id'],
'account_id': client['property_account_receivable_id'][0],
'invoice_line_ids': [(0, False, {'name': "AAA"})]
}])
models.exec_workflow(
db, uid, password, 'account.invoice', 'invoice_open', invoice_id)
$client = $models->execute_kw(
$db, $uid, $password,
'res.partner', 'search_read',
array(array(array('customer', '=', true))),
array(
'limit' => 1,
'fields' => array(
'property_account_receivable_id',
'property_payment_term_id',
'property_account_position_id'
)))[0];
$invoice_id = $models->execute_kw(
$db, $uid, $password,
'account.invoice', 'create', array(array(
'partner_id' => $client['id'],
'account_id' => $client['property_account_receivable_id'][0],
'invoice_line_ids' => array(array(0, false, array('name' => "AAA")))
)));
$models->exec_workflow(
$db, $uid, $password,
'account.invoice', 'invoice_open',
$invoice_id);
client = models.execute_kw(
db, uid, password,
'res.partner', 'search_read',
[[['customer', '=', true]]],
{limit: 1, fields: %w(property_account_receivable_id property_payment_term_id property_account_position_id)}
)[0]
invoice_id = models.execute_kw(
db, uid, password,
'account.invoice', 'create', [{
partner_id: client['id'],
account_id: client['property_account_receivable_id'][0],
invoice_line_ids: [[0, false, {name: "AAA"}]]
}])
models.exec_workflow(
db, uid, password,
'account.invoice', 'invoice_open', invoice_id)
final Map<String, Object> c = (Map<String, Object>)
((Object[])models.execute("execute_kw", asList(
db, uid, password,
"res.partner", "search_read",
asList(
asList(
asList("customer", "=", true))),
new HashMap<String, Object>() {{
put("limit", 1);
put("fields", asList(
"property_account_receivable_id",
"property_payment_term_id",
"property_account_position_id"
));
}}
)))[0];
final Integer invoice_id = (Integer)models.execute(
"execute_kw", asList(
db, uid, password,
"account.invoice", "create",
asList(new HashMap<String, Object>() {{
put("partner_id", c.get("id"));
put("account_id", ((Object[])c.get("property_account_receivable_id"))[0]);
put("invoice_line_ids", asList(
asList(0, false, new HashMap<String, Object>() {{
put("name", "AAA");
}})
));
}})
));
models.execute(
"exec_workflow", asList(
db, uid, password,
"account.invoice", "invoice_open", invoice_id));
Available reports can be listed by searching the ir.actions.report.xml model, fields of interest being
Reports can be printed over RPC with the following information:
invoice_ids = models.execute_kw(
db, uid, password, 'account.invoice', 'search',
[[('type', '=', 'out_invoice'), ('state', '=', 'open')]])
report = xmlrpclib.ServerProxy('{}/xmlrpc/2/report'.format(url))
result = report.render_report(
db, uid, password, 'account.report_invoice', invoice_ids)
report_data = result['result'].decode('base64')
$invoice_ids = $models->execute_kw(
$db, $uid, $password,
'account.invoice', 'search',
array(array(array('type', '=', 'out_invoice'),
array('state', '=', 'open'))));
$report = ripcord::client("$url/xmlrpc/2/report");
$result = $report->render_report(
$db, $uid, $password,
'account.report_invoice', $invoice_ids);
$report_data = base64_decode($result['result']);
require 'base64'
invoice_ids = models.execute_kw(
db, uid, password,
'account.invoice', 'search',
[[['type', '=', 'out_invoice'], ['state', '=', 'open']]])
report = XMLRPC::Client.new2("#{url}/xmlrpc/2/report").proxy
result = report.render_report(
db, uid, password,
'account.report_invoice', invoice_ids)
report_data = Base64.decode64(result['result'])
final Object[] invoice_ids = (Object[])models.execute(
"execute_kw", asList(
db, uid, password,
"account.invoice", "search",
asList(asList(
asList("type", "=", "out_invoice"),
asList("state", "=", "open")))
));
final XmlRpcClientConfigImpl report_config = new XmlRpcClientConfigImpl();
report_config.setServerURL(
new URL(String.format("%s/xmlrpc/2/report", url)));
final Map<String, Object> result = (Map<String, Object>)client.execute(
report_config, "render_report", asList(
db, uid, password,
"account.report_invoice",
invoice_ids));
final byte[] report_data = DatatypeConverter.parseBase64Binary(
(String)result.get("result"));
Note
the report is sent as PDF binary data encoded in base64, it must be decoded and may need to be saved to disk before use
odoo docs theme based on Bootstrap 3.2 documentation adapted to Sphinx output with branding and color changes.