537 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Сервис интеграции ПП Парус 8 с WEB API
Дополнительный модуль: Интеграция с ЭДО "ДИАДОК" (DIADOC)
*/
//------------------------------
// Подключение внешних библиотек
//------------------------------
const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
const _ = require("lodash"); //Работа с коллекциями и объектами
const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами
const { SDDAUTH_API_CLIENT_ID } = require("./diadoc_config"); //Ключ разработчика
//---------------------
// Глобальные константы
//---------------------
// Список тегов которые должны содержать массив
const tag = [
"DocumentAttachments",
"Signatures",
"CorrectionRequests",
"Receipts",
"Resolutions",
"XmlSignatureRejections",
"RecipientTitles",
"Requests"
];
//------------
// Тело модуля
//------------
//Обернуть содержимое тега в массив
const toArray = (obj, tags) => {
for (const prop in obj) {
const value = obj[prop];
if (tags.indexOf(prop) != -1 && !_.isArray(obj[prop])) {
obj[prop] = JSON.parse("[" + JSON.stringify(value) + "]");
}
if (typeof value === "object") {
toArray(value, tag);
}
}
return obj;
};
//Конвертация в XML
const toXML = obj => {
const builder = new xml2js.Builder();
return builder.buildObject(obj);
};
//Конвертация в XML
const parseXML = xmlDoc => {
return new Promise((resolve, reject) => {
xml2js.parseString(xmlDoc, { explicitArray: false, mergeAttrs: true }, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
};
//Конвертация в JSON
const toJSON = async obj => {
let result = await parseXML(obj);
result = result.root;
toArray(result, tag);
return result;
};
//Добавление определённого количетсва часов к дате
const addHours = (dDate, nHours) => {
dDate.setTime(dDate.getTime() + nHours * 60 * 60 * 1000);
return new Date(dDate);
};
//Проверка ключа разработчика
const checkAPIClientId = sAPIClientId => {
if (!sAPIClientId) {
throw new Error('Не задан ключ разработчика. Запросите его у поставщика услуг ЭДО "ДИАДОК" и укажите в "./modules/diadoc_config.js".');
}
};
//Формиорвание заголовка сообщения
const buildHeaders = (sAPIClientId, sToken = null) => ({
"Content-type": "application/json; charset=utf-8",
Authorization: `DiadocAuth ddauth_api_client_id=${sAPIClientId}${sToken ? `,ddauth_token=${sToken}` : ""}`,
Accept: "application/json; charset=utf-8"
});
//Обработчик "До" подключения к сервису
const beforeConnect = async prms => {
//Подготовим параметры аутентификации
const loginAtribute = "login";
const passAtribute = "password";
let surl = prms.options.url;
surl = surl + "?" + "type=password";
//Проверим ключ разработчика
checkAPIClientId(SDDAUTH_API_CLIENT_ID);
//Сформируем запрос на аутентификацию
return {
options: {
headers: buildHeaders(SDDAUTH_API_CLIENT_ID),
url: surl,
body: JSON.stringify({
[loginAtribute]: prms.service.sSrvUser,
[passAtribute]: prms.service.sSrvPass
}),
simple: false
}
};
};
//Обработчик "После" подключения к сервису
const afterConnect = async prms => {
//Если пришла ошибка
if (prms.optionsResp.statusCode != 200) {
throw new Error(prms.queue.blResp.toString());
} else {
//Сохраним полученный токен доступа в контекст сервиса
return {
blResp: Buffer.from(prms.queue.blResp),
sCtx: prms.queue.blResp.toString(),
dCtxExp: addHours(new Date(), 23)
};
}
};
//Обработчик "До" отправки запроса на экспорт документа к сервису "ДИАДОК"
const beforeMessagePost = async prms => {
//Проверим ключ разработчика
checkAPIClientId(SDDAUTH_API_CLIENT_ID);
//Формируем запрос
try {
//Считаем токен доступа из контекста сервиса
let sToken = null;
if (prms.service.sCtx) {
sToken = prms.service.sCtx;
}
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
if (!sToken) return { bUnAuth: true };
//Конвертируем XML из "Парус 8" в JSON
let obj = await toJSON(prms.queue.blMsg.toString());
//Формируем запрос для получения FromBoxId
let rqpoptions = {
uri: "https://diadoc-api.kontur.ru/GetMyOrganizations",
headers: buildHeaders(SDDAUTH_API_CLIENT_ID, sToken),
json: true
};
let serverResp;
try {
//Выполним запрос
serverResp = await rqp(rqpoptions);
//Не удалось получить ящик отправителя
if (!serverResp.Organizations[0].Boxes[0].BoxId) {
throw new Error("Не удалось получить ящик отправителя");
}
//Сохраняем полученный ответ
obj.FromBoxId = serverResp.Organizations[0].Boxes[0].BoxId;
} catch (e) {
throw Error(`Не удалось получить ящик отправителя: ${e.message}`);
}
//Очистим предыдущий запрос
rqpoptions = null;
serverResp = null;
//Формируем запрос для получения ToBoxId
rqpoptions = {
uri: "https://diadoc-api.kontur.ru/GetOrganizationsByInnKpp",
qs: {
inn: prms.options.inn,
kpp: prms.options.kpp
},
headers: buildHeaders(SDDAUTH_API_CLIENT_ID, sToken),
json: true
};
try {
//Выполним запрос
serverResp = await rqp(rqpoptions);
//Не удалось получить ящик получателя
if (!serverResp.Organizations[0].Boxes[0].BoxId) {
throw new Error(`Не удалось получить ящик получателя для контрагента с ИНН: ${prms.options.inn} и КПП: ${prms.options.kpp}`);
}
//Сохраняем полученный ответ
obj.ToBoxId = serverResp.Organizations[0].Boxes[0].BoxId;
} catch (e) {
throw Error(`Не удалось получить ящик получателя для контрагента с ИНН: ${prms.options.inn} и КПП: ${prms.options.kpp}: ${e.message}`);
}
//Если пришел ответ
if (prms.queue.blResp && serverResp.statusCode == 200) {
//Вернем загруженный документ
return {
blResp: prms.queue.blResp
};
}
//Собираем и отдаём общий результат работы
return {
options: {
headers: buildHeaders(SDDAUTH_API_CLIENT_ID, sToken),
simple: false
},
blMsg: Buffer.from(JSON.stringify(obj))
};
} catch (e) {
throw Error(e);
}
};
//Обработчик "После" запроса на экспорт документа к сервису "ДИАДОК"
const afterMessagePost = async prms => {
//Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
let resu = null;
//Действие выполнено успешно
if (prms.optionsResp.statusCode == 200) {
try {
resu = toXML(JSON.parse(prms.queue.blResp.toString()));
} catch (e) {
throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК". Ошибка интерпретации: ${e.message}`);
}
} else {
//Если пришел текст ошибки
if (prms.queue.blResp) {
throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК": ${prms.queue.blResp.toString()}`);
} else {
throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ');
}
}
//Возврат результата
return {
blResp: Buffer.from(resu)
};
};
//Обработчик "До" отправки запроса на экспорт патча документа к сервису "ДИАДОК"
const beforeMessagePatchPost = async prms => {
//Проверим ключ разработчика
checkAPIClientId(SDDAUTH_API_CLIENT_ID);
//Формируем запрос
try {
//Считаем токен доступа из контекста сервиса
let sToken = null;
if (prms.service.sCtx) {
sToken = prms.service.sCtx;
}
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
if (!sToken) return { bUnAuth: true };
//Конвертируем XML из "Парус 8" в понятный "ДИАДОК" JSON
let obj = await toJSON(prms.queue.blMsg.toString());
//Собираем и отдаём общий результат работы
return {
options: {
headers: buildHeaders(SDDAUTH_API_CLIENT_ID, sToken),
simple: false
},
blMsg: Buffer.from(JSON.stringify(obj))
};
} catch (e) {
throw Error(e);
}
};
//Обработчик "После" запроса на экспорт патча документа к сервису "ДИАДОК"
const afterMessagePatchPost = async prms => {
let resu = null;
//Действие выполнено успешно
if (prms.optionsResp.statusCode == 200) {
try {
//Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
resu = toXML(JSON.parse(prms.queue.blResp.toString()));
} catch (e) {
throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК". Ошибка интерпретации: ${e.message}`);
}
} else {
//Если пришел текст ошибки
if (prms.queue.blResp) {
throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК": ${prms.queue.blResp.toString()}`);
} else {
throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ');
}
}
//Возврат результата
return {
blResp: Buffer.from(resu)
};
};
//Обработчик "До" отправки запроса на получение новых событий к сервису "ДИАДОК"
const beforeEvent = async prms => {
//Проверим ключ разработчика
checkAPIClientId(SDDAUTH_API_CLIENT_ID);
//Формируем запрос
try {
//Считаем токен доступа из контекста сервиса
let sToken = null;
if (prms.service.sCtx) {
sToken = prms.service.sCtx;
}
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
if (!sToken) return { bUnAuth: true };
//Формируем запрос для получения BoxId
let rqpoptions = {
uri: "https://diadoc-api.kontur.ru/GetMyOrganizations",
headers: buildHeaders(SDDAUTH_API_CLIENT_ID, sToken),
json: true
};
//Выполним запрос
let serverResp = await rqp(rqpoptions);
//Не удалось получить текущий ящик организации
if (!serverResp.Organizations[0].Boxes[0].BoxId) {
throw new Error("Не удалось получить ящик текущей организации");
}
let surl = prms.options.url;
let obj;
let rblMsg;
surl = surl + "?" + "boxId=" + serverResp.Organizations[0].Boxes[0].BoxId;
//Если действие не "Документооборот"
if (prms.options.saction != "DOCFLOWS") {
if (prms.options.aftereventid) {
surl = surl + "&" + "afterEventId=" + prms.options.aftereventid;
} else {
surl = surl + "&" + "timestampFromTicks=" + prms.options.timestampfromticks;
}
} else {
if (prms.queue.blMsg) {
//Конвертируем XML из "Парус 8" в понятный "ДИАДОК" JSON
obj = await toJSON(prms.queue.blMsg.toString());
rblMsg = Buffer.from(JSON.stringify(obj));
}
}
//Собираем и отдаём общий результат работы
return {
options: {
headers: buildHeaders(SDDAUTH_API_CLIENT_ID, sToken),
simple: false,
url: surl,
boxId: serverResp.Organizations[0].Boxes[0].BoxId
},
blMsg: rblMsg
};
} catch (e) {
throw Error(e);
}
};
//Обработчик "После" запроса на получение новых событий к сервису "ДИАДОК"
const afterEvent = async prms => {
let resu = null;
//Действие выполнено успешно
if (prms.optionsResp.statusCode == 200) {
try {
//Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
resu = toXML({ root: JSON.parse(prms.queue.blResp.toString()) });
} catch (e) {
throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК". Ошибка интерпретации: ${e.message}`);
}
} else {
//Если пришел текст ошибки
if (prms.queue.blResp) {
throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК": ${prms.queue.blResp.toString()}`);
} else {
throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ');
}
}
//Возврат результата
return {
blResp: Buffer.from(resu)
};
};
//Обработчик "До" отправки запроса на загрузку вложения
const beforeDocLoad = async prms => {
//Проверим ключ разработчика
checkAPIClientId(SDDAUTH_API_CLIENT_ID);
//Формируем запрос
try {
//Считаем токен доступа из контекста сервиса
let sToken = null;
if (prms.service.sCtx) {
sToken = prms.service.sCtx;
}
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
if (!sToken) return { bUnAuth: true };
let surl = prms.options.url;
let entId;
let msgId = "messageId=";
//В зависимости от режима загрузки определим наименование узла
switch (prms.options.type) {
//Загрузка файла
case 0:
entId = "entityId=";
break;
//Загрузка PDF
case 1:
entId = "documentId=";
break;
//Загрузка Извещения о получении
case 2:
entId = "attachmentId=";
break;
//Загрузка Уведомления об уточнении
case 3:
entId = "attachmentId=";
break;
//Загрузка Титул отказа от подписи документа
case 4:
entId = "attachmentId=";
break;
//Загрузка Титула покупателя документа
case 5:
entId = "documentId=";
msgId = "letterId=";
break;
default:
}
surl = surl + "?" + msgId + prms.options.smsgid;
surl = surl + "&" + entId + prms.options.sentid;
let obj;
let rblMsg;
if (prms.queue.blMsg && prms.options.type != 5) {
//Конвертируем XML из "Парус 8" в понятный "ДИАДОК" JSON
obj = await toJSON(prms.queue.blMsg.toString());
rblMsg = Buffer.from(JSON.stringify(obj));
} else {
if (prms.queue.blMsg) {
rblMsg = prms.queue.blMsg;
}
}
//Собираем и отдаём общий результат работы
return {
options: {
qs: {
boxId: prms.options.sboxid,
documentTypeNamedId: prms.options.documentTypeNamedId,
documentFunction: prms.options.documentFunction,
documentVersion: prms.options.documentVersion,
titleIndex: prms.options.titleIndex
},
headers: buildHeaders(SDDAUTH_API_CLIENT_ID, sToken),
url: surl,
simple: false
},
blMsg: rblMsg
};
} catch (e) {
throw Error(e);
}
};
//Обработчик "После" отправки запроса на загрузку вложения
const afterDocLoad = async prms => {
if (prms.queue.blResp) {
//Если выполнено без ошибок и не требуется повторный запрос
if ((prms.optionsResp.statusCode == 200 || prms.optionsResp.statusCode == 404) && !prms.optionsResp.headers["retry-after"]) {
return;
} else {
let iterable = [1, 2, 3, 4, 5];
let serverResp;
//Если не превышает лимита запросов
for (let value of iterable) {
if (prms.optionsResp.statusCode != 200 || prms.optionsResp.headers["retry-after"]) {
//Если загружаем PDF
if (prms.options.type == 1 && prms.optionsResp.headers["retry-after"]) {
await new Promise(resolve => setTimeout(resolve, (Number(prms.optionsResp.headers["retry-after"]) + 1) * 1000));
} else {
await new Promise(resolve => setTimeout(resolve, 2000));
}
//Выполним повторный запрос
serverResp = await rqp(prms.options);
//Сохраняем полученный ответ
prms.queue.blResp = Buffer.from(serverResp.body || "");
prms.optionsResp.statusCode = serverResp.statusCode;
prms.optionsResp.headers = serverResp.headers;
//Если пришел ответ
if (prms.queue.blResp && serverResp.statusCode == 200) {
//Вернем загруженный документ
return {
optionsResp: prms.optionsResp,
blResp: prms.queue.blResp
};
}
}
}
//Если был ответ от сервера с ошибкой (иначе мы сюда не попадём)
if (prms.queue.blResp) {
//Разберем сообщение об ошибке
throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК": ${prms.queue.blResp.toString()}`);
}
}
} else {
throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ');
}
//Возврат результата
return;
};
//Обработчик "До" отправки запроса на удаление документа к сервису "ДИАДОК"
const beforeDocDelete = async prms => {
//Проверим ключ разработчика
checkAPIClientId(SDDAUTH_API_CLIENT_ID);
//Формируем запрос
try {
//Считаем токен доступа из контекста сервиса
let sToken = null;
if (prms.service.sCtx) {
sToken = prms.service.sCtx;
}
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
if (!sToken) return { bUnAuth: true };
//Собираем и отдаём общий результат работы
return {
options: {
headers: buildHeaders(SDDAUTH_API_CLIENT_ID, sToken),
simple: false
}
};
} catch (e) {
throw Error(e);
}
};
//-----------------
// Интерфейс модуля
//-----------------
exports.beforeConnect = beforeConnect;
exports.afterConnect = afterConnect;
exports.beforeMessagePost = beforeMessagePost;
exports.afterMessagePost = afterMessagePost;
exports.beforeMessagePatchPost = beforeMessagePatchPost;
exports.afterMessagePatchPost = afterMessagePatchPost;
exports.beforeEvent = beforeEvent;
exports.afterEvent = afterEvent;
exports.beforeDocLoad = beforeDocLoad;
exports.afterDocLoad = afterDocLoad;
exports.beforeDocDelete = beforeDocDelete;