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.
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 async
và await
trong JavaScript ES6 để tinh gọn hơn.
Mục lục
Đă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 name
và Access 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, n | Cá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); } })();