Комплексное решение по входящим и исходящим звонкам
Данный сценарий интеграци работает комплексно. Сценарии подключаются по очереди. Представленный ниже код необходимо добавлять в указанные этапы функций. Логика работы следующая:
- Поиск контакта с ответственным менеджером сопоставленным со схемой( в дальнейшем Пользователь№1=Схема№1) наличие активной сделки у данного контакта
- Нет контакта с Пользователем№1=схема№1, создаем новый контакт и сделку с ответственным Пользователем№1
- Есть контакт но ответственный Пользователем№2 а не Пользователем№1 который привязан схеме создаем (дубль) новый контакт и новую сделку с Пользователем№1
- Если есть контакт с Пользователем№1=схема№1 но нет активной сделки, создаем новую сделку с ответственным Пользователем№1
- Добавляем запись звонка в этот контакт (который подходит по условиям в пункте 1, вновь созданный или найденный с нужным ответственным)
- Если звонок был пропущен, задачу на перезвон добавляем в этот контакт (который подходит по условиям в пункте 1, вновь созданный или найденный с нужным ответственным)
- По Завершению звонка добавляем запись звонка.
- При исходящем такая же логика проверки как в 1 пункте.
Завершен исходящий разговор
const axios = require('axios');
module.exports = async (args) => {
const contactId = args.context.contactId;
const contactName = args.context.contactName;
const contactResponsibleId = args.context.contactResponsibleId;
// пишем звонок
const url = `https://${args.settings.domain}/api/v2/notes`;
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${args.crm_auth}`,
};
const data = {
add: [{
element_id: parseInt(contactId),
element_type: 1, // 1 - Contact, 2 - Lead, 3 - Company
note_type: 11, // 10 - Incoming call, 11 - Outgoing call
params: {
UNIQ: args.call_args.call_id,
LINK: args.call_args.call_record_link,
PHONE: contactName,
DURATION: parseInt(args.call_args.timestamp) - parseInt(args.call_args.call_answer_timestamp),
SRC: 'sipuni',
call_status: 4, //1 Left a voice message, 2 Call back later, 3 Not available, 4 The conversation took place, 5 Wrong number, 6 Did not get through, 7 Number is busy
call_result: '',
},
created_by: contactResponsibleId,
responsible_user_id: contactResponsibleId,
}]
};
const call = await axios.post(url, data, { headers });
const noteId = call.data._embedded.items[0].id;
return { noteId };
};
Исходящий звонок
const SipuniAmoCrm = require('@sipuni/amocrm');
const LEAD_CLOSED_STATUSES = [142 /*успешная*/, 143 /*неуспешная*/];
const CONTACT_LIMIT = 10;
const CONTACT_PHONE_FIELD_ID = 000001; //id телефонного поля
const SCHEME_TO_RESPONSIBLE = { // сопоставление названия схемы и ID ответственного в амо по ней
"Пользователем№1": 111000,
"Пользователем№2": 000222,
"Пользователем№1": 333000,
};
const MAIN_TREE_TAMES = [
'Загород',
];
// запрос информации о сделке по айди, проверка статуса сделки
async function findOpenLead(api, lead_id, reponsible_id = 0) {
const lead = await api.leads.get(lead_id);
if (reponsible_id) {
if (lead && !LEAD_CLOSED_STATUSES.includes(lead.status_id) && lead.responsible_user_id === reponsible_id) {
return lead_id;
}
} else {
if (lead && !LEAD_CLOSED_STATUSES.includes(lead.status_id)) {
return lead_id;
}
}
return 0;
}
// перебор всех найденных контактов, поиск нужного кастомного поля, если нашли, забираем информацию о окнтакте и ищем у него открытую сделку, если не наши возвращаем 0
async function findContactAndLead(amoApi, contacts, tree_name) {
const reponsibleId = SCHEME_TO_RESPONSIBLE[tree_name];
let leads;
const promises = [];
if (MAIN_TREE_TAMES.includes(tree_name)) {
leads = contacts[0]._embedded.leads;
for(let leadIndex=0; leadIndex < leads.length; leadIndex++) {
promises.push(findOpenLead(amoApi, leads[leadIndex].id));
}
const results = await Promise.all(promises);
const leadId = results.find(item => item > 0)
if (leadId) {
return {
contactId: contacts[0].id,
contactName: contacts[0].name,
contactResponsibleId : contacts[0].responsible_user_id,
leadId,
}
} else {
return {
contactId: contacts[0].id,
contactName: contacts[0].name,
contactResponsibleId : contacts[0].responsible_user_id,
leadId: 0,
}
}
}
for(let contactIndex=0; contactIndex < contacts.length; contactIndex++) {
leads = contacts[contactIndex]._embedded.leads;
if (contacts[contactIndex].responsible_user_id === reponsibleId) {
for(let leadIndex=0; leadIndex < leads.length; leadIndex++) {
promises.push(findOpenLead(amoApi, leads[leadIndex].id, reponsibleId));
}
const results = await Promise.all(promises);
const leadId = results.find(item => item > 0)
if (leadId) {
return {
contactId: contacts[contactIndex].id,
contactName: contacts[contactIndex].name,
contactResponsibleId : contacts[contactIndex].responsible_user_id,
leadId,
}
} else {
return {
contactId: contacts[contactIndex].id,
contactName: contacts[contactIndex].name,
contactResponsibleId : contacts[contactIndex].responsible_user_id,
leadId: 0,
}
}
}
}
return {
contactId: 0,
contactName: '',
contactResponsibleId: 0,
leadId: 0,
};
}
module.exports = async (args) => {
const amoApi = new SipuniAmoCrm({ domain: args.settings.domain, accessToken: args.crm_auth });
let contacts = {};
let contactId = 0;
let contactName = '';
let contactResponsibleId = 0;
let leadId = 0;
const reponsibleId = SCHEME_TO_RESPONSIBLE[args.call_args.tree_name];
// Поиск контактов по номеру с айдишниками сделок
// result._embedded.contacts[i]._embedded.leads[j].id
const result = await amoApi.request('GET', '/contacts', {
'query': args.call_args.dst_num.slice(1),
'limit': CONTACT_LIMIT,
'with': 'leads'
});
if (result) {
contacts = result._embedded.contacts;
}
// проверка соответствия доп поля и схемы
const findResult = await findContactAndLead(amoApi, contacts, args.call_args.tree_name);
contactId = findResult.contactId;
contactName = findResult.contactName;
contactResponsibleId = findResult.contactResponsibleId;
leadId = findResult.leadId;
console.log(contactId);
console.log(leadId);
// создание контакта, если не найден существующий
if (!contactId) {
contactName = `Контакт ${args.call_args.dst_num}`;
const contact = await amoApi.contacts.create({
name: contactName,
responsible_user_id: reponsibleId,
custom_fields_values: [
{
field_id: CONTACT_PHONE_FIELD_ID,
values: [{
value: `+${args.call_args.dst_num}`,
enum_code: 'WORK'
}],
},
],
});
contactId = contact.id;
contactResponsibleId = reponsibleId;
}
// создание сделки, если не найдена существующая
if (!leadId) {
const leadName = `Сделка ${args.call_args.dst_num}`;
const lead = await amoApi.leads.create({
name: leadName,
responsible_user_id: reponsibleId,
_embedded: {
contacts: [{
id: contactId,
is_main: true,
}],
},
});
leadId = lead.id;
}
return {
contactId,
contactName,
contactResponsibleId,
leadId
};
};
Исходящий вызов не отвечен
const axios = require('axios');
module.exports = async (args) => {
const contactId = args.context.contactId;
const contactName = args.context.contactName;
const contactResponsibleId = args.context.contactResponsibleId;
// создание заметки о пропущенном
const url = `https://${args.settings.domain}/api/v2/notes`;
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${args.crm_auth}`,
};
const data = {
add: [{
element_id: parseInt(contactId),
element_type: 1, // 1 - Contact, 2 - Lead, 3 - Company
note_type: 11, // 10 - Incoming call, 11 - Outgoing call
params: {
UNIQ: args.call_args.call_id,
PHONE: contactName,
DURATION: 0,
SRC: 'sipuni',
call_status: 6, //1 Left a voice message, 2 Call back later, 3 Not available, 4 The conversation took place, 5 Wrong number, 6 Did not get through, 7 Number is busy
call_result: 'Не дозвонились',
},
created_by: contactResponsibleId,
responsible_user_id: contactResponsibleId,
}]
};
const call = await axios.post(url, data, { headers });
const noteId = call.data._embedded.items[0].id;
return { noteId };
};
Пропущен входящий вызов
const axios = require('axios');
const SipuniAmoCrm = require('@sipuni/amocrm');
const TASK_DUE = 60*60*3600; // 1 час в секундах
module.exports = async (args) => {
const amoApi = new SipuniAmoCrm({ domain: args.settings.domain, accessToken: args.crm_auth });
const contactId = args.context.contactId;
const contactName = args.context.contactName;
const contactResponsibleId = args.context.contactResponsibleId;
const leadId = args.context.leadId;
let task;
let taskId;
// создание заметки о пропущенном
const url = `https://${args.settings.domain}/api/v2/notes`;
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${args.crm_auth}`,
};
const data = {
add: [{
element_id: parseInt(contactId),
element_type: 1, // 1 - Contact, 2 - Lead, 3 - Company
note_type: 10, // 10 - Incoming call, 11 - Outgoing call
params: {
UNIQ: args.call_args.call_id,
PHONE: contactName,
DURATION: 0,
SRC: 'sipuni',
call_status: 6, //1 Left a voice message, 2 Call back later, 3 Not available, 4 The conversation took place, 5 Wrong number, 6 Did not get through, 7 Number is busy
call_result: 'Пропущенный звонок',
},
created_by: contactResponsibleId,
responsible_user_id: contactResponsibleId,
}]
};
const call = await axios.post(url, data, { headers });
const noteId = call.data._embedded.items[0].id;
// создание задачи
const existingTasks = await amoApi.tasks.list({
'filter[responsible_user_id]': contactResponsibleId,
'filter[is_completed]': 0,
'filter[entity_type]': 'leads',
'filter[entity_id]': leadId,
});
if (existingTasks) {
taskId = existingTasks.map(function(task) {return task.id;});
} else {
const timestamp = Math.ceil((new Date()).getTime()/1000);
task = await amoApi.tasks.create({
entity_id: leadId,
entity_type: 'leads',
text: 'Перезвонить',
complete_till: timestamp + TASK_DUE,
responsible_user_id: contactResponsibleId,
});
}
if (task) {
taskId = task.id;
return {
noteId,
createdTaskId: taskId,
};
} else {
return {
noteId,
findedTaskId: taskId,
};
}
};
Входящий звонок на внешний номер
const SipuniAmoCrm = require('@sipuni/amocrm');
const LEAD_CLOSED_STATUSES = [142 /*успешная*/, 143 /*неуспешная*/];
const CONTACT_LIMIT = 10;
const CONTACT_PHONE_FIELD_ID = 000001; // id телефонного поля
const SCHEME_TO_RESPONSIBLE = { // сопоставление названия схемы и ID ответственного в амо по ней
"Пользователем№1": 111000,
"Пользователем№2": 000222,
"Пользователем№1": 333000,
};
const MAIN_TREE_TAMES = [
'Общая',
];
// запрос информации о сделке по айди, проверка статуса сделки
async function findOpenLead(api, lead_id, reponsible_id = 0) {
const lead = await api.leads.get(lead_id);
if (reponsible_id) {
if (lead && !LEAD_CLOSED_STATUSES.includes(lead.status_id) && lead.responsible_user_id === reponsible_id) {
return lead_id;
}
} else {
if (lead && !LEAD_CLOSED_STATUSES.includes(lead.status_id)) {
return lead_id;
}
}
return 0;
}
// перебор всех найденных контактов, поиск нужного кастомного поля, если нашли, забираем информацию о окнтакте и ищем у него открытую сделку, если не наши возвращаем 0
async function findContactAndLead(amoApi, contacts, tree_name) {
const reponsibleId = SCHEME_TO_RESPONSIBLE[tree_name];
let leads;
const promises = [];
if (MAIN_TREE_TAMES.includes(tree_name)) {
leads = contacts[0]._embedded.leads;
for(let leadIndex=0; leadIndex < leads.length; leadIndex++) {
promises.push(findOpenLead(amoApi, leads[leadIndex].id));
}
const results = await Promise.all(promises);
const leadId = results.find(item => item > 0)
if (leadId) {
return {
contactId: contacts[0].id,
contactName: contacts[0].name,
contactResponsibleId : contacts[0].responsible_user_id,
leadId,
}
} else {
return {
contactId: contacts[0].id,
contactName: contacts[0].name,
contactResponsibleId : contacts[0].responsible_user_id,
leadId: 0,
}
}
}
for(let contactIndex=0; contactIndex < contacts.length; contactIndex++) {
leads = contacts[contactIndex]._embedded.leads;
if (contacts[contactIndex].responsible_user_id === reponsibleId) {
for(let leadIndex=0; leadIndex < leads.length; leadIndex++) {
promises.push(findOpenLead(amoApi, leads[leadIndex].id, reponsibleId));
}
const results = await Promise.all(promises);
const leadId = results.find(item => item > 0)
if (leadId) {
return {
contactId: contacts[contactIndex].id,
contactName: contacts[contactIndex].name,
contactResponsibleId : contacts[contactIndex].responsible_user_id,
leadId,
}
} else {
return {
contactId: contacts[contactIndex].id,
contactName: contacts[contactIndex].name,
contactResponsibleId : contacts[contactIndex].responsible_user_id,
leadId: 0,
}
}
}
}
return {
contactId: 0,
contactName: '',
contactResponsibleId: 0,
leadId: 0,
};
}
module.exports = async (args) => {
const amoApi = new SipuniAmoCrm({ domain: args.settings.domain, accessToken: args.crm_auth });
let contacts = {};
let contactId = 0;
let contactName = '';
let contactResponsibleId = 0;
let leadId = 0;
const reponsibleId = SCHEME_TO_RESPONSIBLE[args.call_args.tree_name];
// Поиск контактов по номеру с айдишниками сделок
// result._embedded.contacts[i]._embedded.leads[j].id
const result = await amoApi.request('GET', '/contacts', {
'query': args.call_args.src_num.slice(1),
'limit': CONTACT_LIMIT,
'with': 'leads'
});
if (result) {
contacts = result._embedded.contacts;
}
// проверка соответствия доп поля и схемы
const findResult = await findContactAndLead(amoApi, contacts, args.call_args.tree_name);
contactId = findResult.contactId;
contactName = findResult.contactName;
contactResponsibleId = findResult.contactResponsibleId;
leadId = findResult.leadId;
// создание контакта, если не найден существующий
if (!contactId) {
contactName = `Контакт ${args.call_args.src_num}`;
const contact = await amoApi.contacts.create({
name: contactName,
responsible_user_id: reponsibleId,
custom_fields_values: [
{
field_id: CONTACT_PHONE_FIELD_ID,
values: [{
value: `+${args.call_args.src_num}`,
enum_code: 'WORK'
}],
},
],
});
contactId = contact.id;
contactResponsibleId = reponsibleId;
}
// создание сделки, если не найдена существующая
if (!leadId) {
const leadName = `Сделка ${args.call_args.src_num}`;
const lead = await amoApi.leads.create({
name: leadName,
responsible_user_id: reponsibleId,
_embedded: {
contacts: [{
id: contactId,
is_main: true,
}],
},
});
leadId = lead.id;
}
return {
contactId,
contactName,
contactResponsibleId,
leadId
};
};
Завершен входящий разговор
const axios = require('axios');
module.exports = async (args) => {
const contactId = args.context.contactId;
const contactName = args.context.contactName;
const contactResponsibleId = args.context.contactResponsibleId;
// пишем звонок
const url = `https://${args.settings.domain}/api/v2/notes`;
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${args.crm_auth}`,
};
const data = {
add: [{
element_id: parseInt(contactId),
element_type: 1, // 1 - Contact, 2 - Lead, 3 - Company
note_type: 10, // 10 - Incoming call, 11 - Outgoing call
params: {
UNIQ: args.call_args.call_id,
LINK: args.call_args.call_record_link,
PHONE: contactName,
DURATION: parseInt(args.call_args.timestamp) - parseInt(args.call_args.call_answer_timestamp),
SRC: 'sipuni',
call_status: 4, //1 Left a voice message, 2 Call back later, 3 Not available, 4 The conversation took place, 5 Wrong number, 6 Did not get through, 7 Number is busy
call_result: '',
},
created_by: contactResponsibleId,
responsible_user_id: contactResponsibleId,
}]
};
const call = await axios.post(url, data, { headers });
const noteId = call.data._embedded.items[0].id;
return { noteId };
};