From 52b337b02b76d17099d65439b8cec9b5a18fd1bb Mon Sep 17 00:00:00 2001 From: Mikhail Chechnev Date: Fri, 30 Sep 2022 18:54:51 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A6=D0=98=D0=A2=D0=9A-171=20-=20=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BE=D0=B5=20=D1=80=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20"=D0=98=D0=BD=D1=82=D0=B5=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=20=D0=AD=D0=94=D0=9E=20=D0=94?= =?UTF-8?q?=D0=98=D0=90=D0=94=D0=9E=D0=9A"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/diadoc.js | 522 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 522 insertions(+) create mode 100644 modules/diadoc.js diff --git a/modules/diadoc.js b/modules/diadoc.js new file mode 100644 index 0000000..9f3377e --- /dev/null +++ b/modules/diadoc.js @@ -0,0 +1,522 @@ +/* + Сервис интеграции ПП Парус 8 с WEB API + Дополнительный модуль: Интеграция с ЭДО "ДИАДОК" (DIADOC) +*/ + +//------------------------------ +// Подключение внешних библиотек +//------------------------------ + +const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML +const _ = require("lodash"); //Работа с коллекциями и объектами +const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами +const Ddauth_Api_Client_Id = "citk-5d8e1345-3f33-496b-a878-9309011dd6c9"; //Ключ разработчика + +//--------------------- +// Глобальные константы +//--------------------- + +// Список тегов которые должны содержать массив +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 beforeConnect = async prms => { + //Подготовим параметры аутентификации + const loginAtribute = "login"; + const passAtribute = "password"; + //Сформируем запрос на аутентификацию + return { + options: { + qs: { + type: "password" + }, + headers: { + "content-type": "application/json;charset=utf-8", + Authorization: "DiadocAuth ddauth_api_client_id=" + Ddauth_Api_Client_Id + }, + 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.blRes.toString()); + } else { + //Сохраним полученный токен доступа в контекст сервиса + return { + blResp: Buffer.from(prms.queue.blResp), + sCtx: prms.queue.blResp.toString(), + dCtxExp: addHours(new Date(), 23) + }; + } +}; + +//Обработчик "До" отправки запроса на экспорт документа к сервису "ДИАДОК" +const beforeMessagePost = async prms => { + 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: { + "Content-type": "application/json; charset=utf-8", + Authorization: "DiadocAuth ddauth_api_client_id=" + Ddauth_Api_Client_Id + ",ddauth_token=" + sToken, + Accept: "application/json; charset=utf-8" + }, + json: true + }; + //Выполним запрос + let serverResp = await rqp(rqpoptions); + //Не удалось получить ящик отправителя + if (!serverResp.Organizations[0].Boxes[0].BoxId) { + throw new Error("Не удалось получить ящик отправителя"); + } + //Сохраняем полученный ответ + obj.FromBoxId = serverResp.Organizations[0].Boxes[0].BoxId; + //Формируем запрос для получения ToBoxId + rqpoptions = { + uri: "https://diadoc-api.kontur.ru/GetOrganizationsByInnKpp", + qs: { + inn: prms.options.inn, + kpp: prms.options.kpp + }, + headers: { + "Content-type": "application/json; charset=utf-8", + Authorization: "DiadocAuth ddauth_api_client_id=" + Ddauth_Api_Client_Id + ",ddauth_token=" + sToken, + Accept: "application/json; charset=utf-8" + }, + json: true + }; + //Выполним запрос + serverResp = await rqp(rqpoptions); + //Не удалось получить ящик получателя + if (!serverResp.Organizations[0].Boxes[0].BoxId) { + throw new Error("Не удалось получить ящик получателя"); + } + //Сохраняем полученный ответ + obj.ToBoxId = serverResp.Organizations[0].Boxes[0].BoxId; + //Если пришел ответ + if (prms.queue.blResp && serverResp.statusCode == 200) { + //Вернем загруженный документ + return { + blResp: prms.queue.blResp + }; + } + //Собираем и отдаём общий результат работы + return { + options: { + headers: { + "Content-type": "application/json; charset=utf-8", + Authorization: "DiadocAuth ddauth_api_client_id=" + Ddauth_Api_Client_Id + ",ddauth_token=" + sToken, + Accept: "application/json; charset=utf-8" + }, + 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 => { + 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: { + "Content-type": "application/json; charset=utf-8", + Authorization: "DiadocAuth ddauth_api_client_id=" + Ddauth_Api_Client_Id + ",ddauth_token=" + sToken, + Accept: "application/json; charset=utf-8" + }, + simple: false + }, + blMsg: Buffer.from(JSON.stringify(obj)) + }; + } catch (e) { + throw Error(e); + } +}; + +//Обработчик "После" запроса на экспорт патча документа к сервису "ДИАДОК" +const afterMessagePatchPost = 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 beforeEvent = async prms => { + 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: { + "Content-type": "application/json; charset=utf-8", + Authorization: "DiadocAuth ddauth_api_client_id=" + Ddauth_Api_Client_Id + ",ddauth_token=" + sToken, + Accept: "application/json; charset=utf-8" + }, + 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: { + "Content-type": "application/json; charset=utf-8", + Authorization: "DiadocAuth ddauth_api_client_id=" + Ddauth_Api_Client_Id + ",ddauth_token=" + sToken, + Accept: "application/json; charset=utf-8" + }, + simple: false, + url: surl, + boxId: serverResp.Organizations[0].Boxes[0].BoxId + }, + blMsg: rblMsg + }; + } catch (e) { + throw Error(e); + } +}; + +//Обработчик "После" запроса на получение новых событий к сервису "ДИАДОК" +const afterEvent = async prms => { + //Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8" + let resu = null; + if (prms.queue.blResp) { + try { + resu = toXML({ root: JSON.parse(prms.queue.blResp.toString()) }); + } catch (e) { + throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК". Ошибка интерпретации: ${e.message}`); + } + } else { + throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ'); + } + //Возврат результата + return { + blResp: Buffer.from(resu) + }; +}; + +//Обработчик "До" отправки запроса на загрузку вложения +const beforeDocLoad = async prms => { + 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: { + "Content-type": "application/json; charset=utf-8", + Authorization: "DiadocAuth ddauth_api_client_id=" + Ddauth_Api_Client_Id + ",ddauth_token=" + sToken, + Accept: "application/json; charset=utf-8" + }, + 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 => { + try { + //Считаем токен доступа из контекста сервиса + let sToken = null; + if (prms.service.sCtx) { + sToken = prms.service.sCtx; + } + //Если не достали из контекста токен доступа - значит нет аутентификации на сервере + if (!sToken) return { bUnAuth: true }; + //Собираем и отдаём общий результат работы + return { + options: { + headers: { + "Content-type": "application/json; charset=utf-8", + Authorization: "DiadocAuth ddauth_api_client_id=" + Ddauth_Api_Client_Id + ",ddauth_token=" + sToken, + Accept: "application/json; charset=utf-8" + }, + 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;