Sử dụng REST API của Vtiger CRM bằng JavaScript

Vtiger CRM là một trong những hệ thống CRM ưa thích của tôi. Tuy nhiên giao diện phức tạp và chậm chạp cùng hệ thống Report khá giới hạn nên tôi thường không sử dụng trực tiếp mà xây dựng bộ giao diện riêng thao tác dữ liệu với CRM qua REST API.

Sử dụng REST API của Vtiger CRM bằng JavaScript
Hướng dẫn sử dụng REST API của Vtiger CRM

Mặc định thì Web Service của Vtiger CRM không hỗ trợ CORS. Nếu muốn sử dụng REST API từ domain khác, bạn cần gửi header Access-Control-Allow-Origin chứa origin URL của app về trình duyệt của client. Cách đơn giản nhất là thêm đoạn code sau vào file cấu hình config.inc.php của Vtiger.

config.inc.php

...
// origin URL of front-end apps
$allowed_origins = [
    "https://www.example-1.com",
    "https://www.example-2.com"
    // ... etc
];

if ( in_array($_SERVER["HTTP_ORIGIN"], $allowed_origins)  ) :
    header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);
endif;
...

Từ phía client browser, bạn có thể thực hiện lời gọi đến REST API bằng JQuery theo Specification.


Dưới đây là một số hàm công cụ (utility functions) cho một số API thường dùng. Các request đều sử dụng asyncawait trong JavaScript ES6 để tinh gọn hơn.

Đăng nhập vào Vtiger CRM

Đăng nhập có lẽ là thao tác phức tạp nhất trong các REST API của Vtiger CRM.

Để đăng nhập bạn cần 2 thông tin là User nameAccess Key của user. Thông tin này có thể tìm thấy trong menu My Preference.

Để tạo ra chuỗi hash token, tôi sử dụng rollups/md5.js trong thư viện CryptoJS.

/**
 * @param  webService The URL of Vtiger CRM Web Service.
 * @param  userName The user name.
 * @param  accessKey The Access Key associated with the user
 * @return the login information if authentication is successful.
 */
async function login(webService, userName, accessKey) {
    let challenge = await $.get(webService, {operation: 'getchallenge', username: userName});
    if (challenge.success && challenge.result.token) {
        // calculate the token key.
        let accessToken = CryptoJS.MD5(challenge.result.token + accessKey);
        let login = await $.post(webService, {operation: 'login', username: userName, accessKey: accessToken.toString()});
        if (login.success && login.result) {
            return login.result;
        } else {
            throw login.error;
        }
    } else {
        throw challenge.error;
    }
}

Đăng xuất (logout)

/**
 * @param  webService The URL of Vtiger CRM Web Service.
 * @param  sessionId The session Id obtained after logging in successfully.
 */
async function logout(webService, sessionId) {
    let logout = await $.post(webService, {operation: 'logout', sessionName: sessionId});
    if (logout.success && logout.result) {
        return logout.result;
    } else {
        throw logout.error;
    }
}

Lấy danh sách các Module (listtypes)

/**
 * @param  webService The URL of Vtiger CRM Web Service.
 * @param  sessionId The session Id obtained after logging in successfully.
 * @return an array of result if query successful.
 */
async function listtypes(webService, sessionId) {
    let response = await $.get(webService, {operation: 'listtypes', sessionName: sessionId});
    if (response.success && response.result) {
        return response.result;
    } else {
        throw response.error;
    }
}

Lấy thông tin mô tả chi tiết của một Module (describe)

/**
 * @param  webService The URL of Vtiger CRM Web Service.
 * @param  sessionId The session Id obtained after logging in successfully.
 * @param moduleName The name of the Module (obtained by 'listtypes')
 * @return an array of result if query successful.
 */
async function describe(webService, sessionId, moduleName) {
    let response = await $.get(webService, {operation: 'describe', sessionName: sessionId, elementType: moduleName});
    if (response.success && response.result) {
        return response.result;
    } else {
        throw response.error;
    }
}

Truy xuất dữ liệu bằng query

/**
 * @param  webService The URL of Vtiger CRM Web Service.
 * @param  sessionId The session Id obtained after logging in successfully.
 * @param  queryString The query string in SQL-like format
 * @return an array of result if query successful.
 */
async function query(webService, sessionId, queryString) {
    let response = await $.get(webService, {operation: 'query', sessionName: sessionId, query: queryString});
    if (response.success && response.result) {
        return response.result;
    } else {
        throw response.error;
    }
}

Cú pháp của chuỗi query như sau:

SELECT * | <column_list> | <COUNT(*)>
        FROM <object>
        [WHERE <conditionals>]
        [ORDER BY <column_list>]
        [LIMIT [<m>, ] <n>]

Do chỉ là subset của SQL, chuỗi query có khá nhiều giới hạn. Ví dụ:

  • Chỉ query trên một Module.
  • Không hỗ trợ JOINS.
  • Số lượng kết quả tối đa mỗi lần query là 100.
  • Không kết hợp logic điều kiện bằng dấu ngoặc ( ) được.
<column_list>Danh sách các trường, ngăn cách bằng dấu phẩy.
<COUNT(*)>Trả về số lượng thay vì dữ liệu.
<object>Tên Module (danh sách lấy từ hàm listtypes).
<conditionals>Toán tử điều kiện (<, >, <=, >=, =, !=), toán tử IN() hoặc toán tử LIKE; kết hợp với nhau bằng ‘AND’ hoặc ‘OR’, thực hiện từ trái qua phải, không nhóm được bằng dấu ngoặc.
<column_list>Danh sách các cột, ngăn cách bằng dấu phẩy.
Nếu lấy theo thứ tự đảo ngược thì thêm DESC
m, nCác số integer chỉ giới hạn của query: offset (bắt đầu từ index nào) và limit (số lượng kết quả, tối đa 100).

Lưu ý là tùy thiết lập quyền (privilege) của user, sẽ có thể chỉ truy cập được vào giới hạn các bảng (Module) hoặc các trường (Field).

Lấy thông tin đầy đủ của một element (retrieve)

Dùng query nhiều khi dữ liệu trả về không đầy đủ. Bạn sẽ gặp điều này khi thao tác trên các Module có trường liên kết với những Module khác, điển hình như Quotes, Orders v.v…

Vì vậy thông thường ta dùng query để lọc và lấy id, sau đó dùng retrieve để lấy dữ liệu chi tiết.

/**
 * @param  webService The URL of Vtiger CRM Web Service.
 * @param  sessionId The session Id obtained after logging in successfully.
 * @param  elementId The ID of the element.
 * @return Full data of the Element if the ID exists.
 */
async function retrieve(webService, sessionId, elementId) {
    let response = await $.get(webService, {operation: 'retrieve', sessionName: sessionId, id: elementId});
    if (response.success && response.result) {
        return response.result;
    } else {
        throw response.error;
    }
}

Tạo một element mới (create)

Trong dữ liệu để tạo một element mới, tất cả các trường bắt buộc (mandatory) đều phải có dữ liệu hợp lệ. Để biết trong một Module, trường nào là trường mandatory và kiểu dữ liệu của nó, bạn hãy sử dụng hàm describe.

/**
 * @param  webService The URL of Vtiger CRM Web Service.
 * @param  sessionId The session Id obtained after logging in successfully.
 * @param  elementType The Module Name in which the new element is created.
 * @param  elementData The data object which consists at least all mandatory fields.
 * @return Data of the newly created result if successful.
 */
async function create(webService, sessionId, elementType, elementData) {
    let response = await $.post(webService, {operation: 'retrieve', sessionName: sessionId, elementType: elementType, element: JSON.stringify(elementData) });
    if (response.success && response.result) {
        return response.result;
    } else {
        throw response.error;
    }
}

Cập nhật thông tin của một element

Để câp nhật thông tin của một element một cách chính xác, thông thường dữ liệu của element sẽ được lấy bằng retrieve trước, chỉnh sửa rồi sau đó mới update. Thông tin quan trọng nhất chính là ID của element.

/**
 * @param  webService The URL of Vtiger CRM Web Service.
 * @param  sessionId The session Id obtained after logging in successfully.
 * @param  elementData The data object which consists at least all mandatory fields and updated data.
 * @return Data of the updated result if successful.
 */
async function update(webService, sessionId, elementData) {
    let response = await $.post(webService, {operation: 'update', sessionName: sessionId, element: JSON.stringify(elementData) });
    if (response.success && response.result) {
        return response.result;
    } else {
        throw response.error;
    }
}

Xóa một element

/**
 * @param  webService The URL of Vtiger CRM Web Service.
 * @param  sessionId The session Id obtained after logging in successfully.
 * @param  elementId The ID of the element to be deleted.
 * @return Data of the deleted result if successful.
 */
async function delete(webService, sessionId, elementId) {
    let response = await $.post(webService, {operation: 'delete', sessionName: sessionId, id: elementId });
    if (response.success && response.result) {
        return response.result;
    } else {
        throw response.error;
    }
}

Ví dụ: Lấy thông tin các Contact có nguồn từ Facebook

Tất nhiên đây chỉ là ví dụ thôi, vì nếu số lượng Contact của bạn lên đến hàng triệu thì cần áp dụng bộ lọc cho query sâu hơn.

var vtigerUrl = "https://crm.example.com";
var webService = vtigerUrl + "/webservice.php";

(async function() {
    let offset = 0, limit = 100;
    try {
        let user = await login(webService, "admin", "my-access-key");

        // Perform a brute-force search
        while (true) {
            let queryString = "SELECT * FROM Contacts " +
                    "WHERE leadsource = 'Facebook' " +
                    "LIMIT " + offset + "," + limit + ";";
            let contacts = await query(webService, user.sessionId, queryString);

            // dump all found contacts' phone number to the console
            contacts.forEach(function(contact) {
                console.log(contact.lastname + ": " + contact.mobile);
            });

            // break the loop if it is out of data
            if (contacts.length < limit)
                break;

            // prepare for the next query
            offset += contacts.length;
        }

        await logout(webService, user.sessionId);
    } catch (e) {
        alert(`Error ${e.code}: ${e.message}`);
        console.error(e.message);
    }
})();

Phản hồi về bài viết

Cùng thảo luận chút nhỉ!

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.