import xmlrpc from 'xmlrpc';
export interface OdooConfig {
host: string;
database: string;
username: string;
apiKey: string;
port?: number;
protocol?: 'http' | 'https';
}
export class OdooConnection {
private config: OdooConfig;
private uid: number | null = null;
private commonClient: any;
private objectClient: any;
constructor(config: OdooConfig) {
// Set default port based on protocol if not provided
const defaultPort = config.protocol === 'https' ? 443 : 8069;
this.config = {
...config,
port: config.port || defaultPort,
protocol: config.protocol || 'http'
};
const baseUrl = `${this.config.protocol}://${this.config.host}:${this.config.port}`;
// Create XML-RPC clients
const clientOptions = {
host: this.config.host,
port: this.config.port,
path: '/xmlrpc/2/common'
};
this.commonClient = this.config.protocol === 'https'
? xmlrpc.createSecureClient(clientOptions)
: xmlrpc.createClient(clientOptions);
this.objectClient = this.config.protocol === 'https'
? xmlrpc.createSecureClient({ ...clientOptions, path: '/xmlrpc/2/object' })
: xmlrpc.createClient({ ...clientOptions, path: '/xmlrpc/2/object' });
}
/**
* Authenticate with Odoo and get the user ID
*/
async authenticate(): Promise<number> {
return new Promise((resolve, reject) => {
this.commonClient.methodCall('authenticate', [
this.config.database,
this.config.username,
this.config.apiKey,
{}
], (error: any, uid: number) => {
if (error) {
reject(new Error(`Authentication failed: ${error.message}`));
} else if (!uid) {
reject(new Error('Authentication failed: Invalid credentials'));
} else {
this.uid = uid;
resolve(uid);
}
});
});
}
/**
* Ensure we're authenticated before making calls
*/
private async ensureAuthenticated(): Promise<number> {
if (!this.uid) {
await this.authenticate();
}
return this.uid!;
}
/**
* Execute a method on an Odoo model
*/
async execute(
model: string,
method: string,
args: any[] = [],
kwargs: Record<string, any> = {}
): Promise<any> {
const uid = await this.ensureAuthenticated();
return new Promise((resolve, reject) => {
this.objectClient.methodCall('execute_kw', [
this.config.database,
uid,
this.config.apiKey,
model,
method,
args,
kwargs
], (error: any, result: any) => {
if (error) {
reject(new Error(`Execute failed: ${error.message}`));
} else {
resolve(result);
}
});
});
}
/**
* Search for records
*/
async search(
model: string,
domain: any[] = [],
options: { offset?: number; limit?: number; order?: string } = {}
): Promise<number[]> {
return this.execute(model, 'search', [domain], options);
}
/**
* Search and read records
*/
async searchRead(
model: string,
domain: any[] = [],
fields: string[] = [],
options: { offset?: number; limit?: number; order?: string } = {}
): Promise<any[]> {
return this.execute(model, 'search_read', [domain], {
fields,
...options
});
}
/**
* Read records by IDs
*/
async read(
model: string,
ids: number[],
fields: string[] = []
): Promise<any[]> {
return this.execute(model, 'read', [ids], { fields });
}
/**
* Create a record
*/
async create(model: string, values: Record<string, any>): Promise<number> {
return this.execute(model, 'create', [values]);
}
/**
* Update records
*/
async write(
model: string,
ids: number[],
values: Record<string, any>
): Promise<boolean> {
return this.execute(model, 'write', [ids, values]);
}
/**
* Delete records
*/
async unlink(model: string, ids: number[]): Promise<boolean> {
return this.execute(model, 'unlink', [ids]);
}
/**
* Get field definitions for a model
*/
async fieldsGet(
model: string,
fields: string[] = [],
attributes: string[] = []
): Promise<any> {
return this.execute(model, 'fields_get', [fields], { attributes });
}
/**
* Get server version info
*/
async version(): Promise<any> {
return new Promise((resolve, reject) => {
this.commonClient.methodCall('version', [], (error: any, result: any) => {
if (error) {
reject(new Error(`Version check failed: ${error.message}`));
} else {
resolve(result);
}
});
});
}
/**
* Check access rights
*/
async checkAccessRights(
model: string,
operation: 'read' | 'write' | 'create' | 'unlink',
raiseException: boolean = false
): Promise<boolean> {
return this.execute(model, 'check_access_rights', [operation, raiseException]);
}
/**
* Get the current user context
*/
async getContext(): Promise<any> {
const uid = await this.ensureAuthenticated();
const user = await this.read('res.users', [uid], ['context_lang', 'tz']);
return user[0];
}
}