From 43c946b16ae5ce6f6dc4cc5ee2b30b0072a868d9 Mon Sep 17 00:00:00 2001 From: Mikhail Chechnev Date: Sat, 29 Dec 2018 20:08:13 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BC=D0=B5=D0=BD=D0=B0=20=D1=81=20=D0=90=D0=A2=D0=9E?= =?UTF-8?q?=D0=9B-=D0=BE=D0=BD=D0=BB=D0=B0=D0=B9=D0=BD=20-=20API=20v4,=20?= =?UTF-8?q?=D0=A4=D0=A4=D0=94=201.05=20(=D0=BF=D0=B5=D1=80=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B1=D0=B5=D1=82=D0=B0=20=D1=83=D1=81=D0=BF=D0=B5?= =?UTF-8?q?=D1=88=D0=BD=D0=BE=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D1=8F=D1=8E=D1=89=D0=B0=D1=8F=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=B8=20=D1=80=D0=B0=D0=B7=D0=B1=D0=B8=D1=80=D0=B0?= =?UTF-8?q?=D1=8E=D1=89=D0=B0=D1=8F=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82)=20?= =?UTF-8?q?=D0=A1=20=D0=9D=D0=BE=D0=B2=D1=8B=D0=BC=20=D0=93=D0=BE=D0=B4?= =?UTF-8?q?=D0=BE=D0=BC=20=D0=BC=D0=B5=D0=BD=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/UDO_PKG_EXS_ATOL.pck | 20 +- db/UDO_P_FISCDOCS_MAKE_MSG_ATOL.prc | 3 + modules/parus_atol_v4_ffd1.05.js | 300 +++++++++++++++++++++++++--- 3 files changed, 295 insertions(+), 28 deletions(-) diff --git a/db/UDO_PKG_EXS_ATOL.pck b/db/UDO_PKG_EXS_ATOL.pck index 3af2bf7..2d99304 100644 --- a/db/UDO_PKG_EXS_ATOL.pck +++ b/db/UDO_PKG_EXS_ATOL.pck @@ -21,11 +21,29 @@ create or replace package body UDO_PKG_EXS_ATOL as ) is REXSQUEUE EXSQUEUE%rowtype; -- + CTMP clob; -- begin /* */ REXSQUEUE := GET_EXSQUEUE_ID(NFLAG_SMART => 0, NRN => NEXSQUEUE); + /* */ + if (REXSQUEUE.LNK_DOCUMENT is null) then + P_EXCEPTION(0, ' .'); + end if; + if (REXSQUEUE.LNK_UNITCODE is null) then + P_EXCEPTION(0, ' .'); + end if; + if (REXSQUEUE.LNK_UNITCODE <> 'UDO_FiscalDocuments') then + P_EXCEPTION(0, + ' "%s", , .', + REXSQUEUE.LNK_UNITCODE); + end if; /* */ - null; + CTMP := BLOB2CLOB(LBDATA => REXSQUEUE.RESP, SCHARSET => 'UTF8'); + if (CTMP is null) then + P_EXCEPTION(0, ' .'); + end if; + /* */ + update UDO_FISCDOCS T set T.NUMB_FD = CTMP where T.RN = REXSQUEUE.LNK_DOCUMENT; end V4_FFD105_PROCESS_REG_BILL_SIR; end; diff --git a/db/UDO_P_FISCDOCS_MAKE_MSG_ATOL.prc b/db/UDO_P_FISCDOCS_MAKE_MSG_ATOL.prc index 4dbb92a..12c53b2 100644 --- a/db/UDO_P_FISCDOCS_MAKE_MSG_ATOL.prc +++ b/db/UDO_P_FISCDOCS_MAKE_MSG_ATOL.prc @@ -190,6 +190,9 @@ begin PKG_EXS.QUEUE_PUT(SEXSSERVICE => SEXSSERVICE, SEXSSERVICEFN => SEXSSERVICEFN, BMSG => CLOB2BLOB(LCDATA => CDATA, SCHARSET => 'UTF8'), + NLNK_COMPANY => NCOMPANY, + NLNK_DOCUMENT => NFISCDOC, + SLNK_UNITCODE => 'UDO_FiscalDocuments', NNEW_EXSQUEUE => NEXSQUEUE); end; / diff --git a/modules/parus_atol_v4_ffd1.05.js b/modules/parus_atol_v4_ffd1.05.js index 65bf559..0697c58 100644 --- a/modules/parus_atol_v4_ffd1.05.js +++ b/modules/parus_atol_v4_ffd1.05.js @@ -1,6 +1,103 @@ /* Сервис интеграции ПП Парус 8 с WEB API Дополнительный модуль: Взаимодействие с "АТОЛ-Онлайн" (v4) в формате ФФД 1.05 + + Полный формат формируемой посылки: + reqBody = { + timestamp: doc.SDDOC_DATE, + external_id: doc.NRN, + service: { + callback_url: "" + }, + receipt: { + client: { + email: "", + phone: "" + }, + company: { + email: "", + sno: "", + inn: "", + payment_address: "" + }, + agent_info: { + type: "", + paying_agent: { + operation: "", + phones: [""] + }, + receive_payments_operator: { + phones: [""] + }, + money_transfer_operator: { + phones: [""], + name: "", + address: "", + inn: "" + } + }, + supplier_info: { + phones: [""] + }, + items: [ + { + name: "", + price: 0, + quantity: 0, + sum: 0, + measurement_unit: "", + payment_method: "", + payment_object: "", + vat: { + type: "", + sum: 0 + }, + agent_info: { + type: "", + paying_agent: { + operation: "", + phones: [""] + }, + receive_payments_operator: { + phones: [""] + }, + money_transfer_operator: { + phones: [""], + name: "", + address: "", + inn: "" + } + }, + supplier_info: { + phones: [""], + name: "", + address: "", + inn: "" + }, + user_data: "" + } + ], + payments: [ + { + type: 0, + sum: 0 + } + ], + vats: [ + { + type: "", + sum: 0 + } + ], + total: 0, + additional_check_props: "", + cashier: "", + additional_user_props: { + name: "", + value: "" + } + } + }; */ //---------------------- @@ -10,6 +107,9 @@ const util = require("util"); //Встроенные вспомогательные утилиты const parseString = require("xml2js").parseString; //Конвертация XML в JSON const _ = require("lodash"); //Работа с массивами и коллекциями +const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами +const { buildURL } = require("@core/utils"); //Вспомогательные функции +const { NFN_TYPE_LOGIN } = require("@models/obj_service_function"); //--------------------- // Глобальные константы @@ -54,6 +154,17 @@ const paymentObject = { } }; +//Словарь - Тип операции +const paymensOperation = { + sName: "Тип операции", + vals: { + "1": "sell", + "2": "sell_refund", + "3": "buy", + "4": "buy_refund" + } +}; + //------------ // Тело модуля //------------ @@ -126,12 +237,51 @@ const getPropValueByCode = (props, sCode, sValType = "STR", sValField = "VALUE") return res; }; +//Подключение к сервису +const connect = async prms => { + let authFn = _.find(prms.service.functions, { nFnType: NFN_TYPE_LOGIN }); + if (!authFn) throw Error(`Для сервиса ${prms.service.sCode} не определена функция аутентификации`); + try { + let serverResp = JSON.parse( + await rqp({ + method: authFn.sFnPrmsType, + url: buildURL({ sSrvRoot: prms.service.sSrvRoot, sFnURL: authFn.sFnURL }), + body: JSON.stringify({ login: prms.service.sSrvUser, pass: prms.service.sSrvPass }), + simple: false + }) + ); + if (serverResp.error === null) { + return serverResp.token; + } else { + throw Error(serverResp.error.text); + } + } catch (e) { + throw Error(`Ошибка аутентификации на сервере АТОЛ-Онлайн: ${e.message}`); + } +}; + //Обработчик "До" отправки запроса на регистрацию чека (приход, расход, возврат) серверу "АТОЛ-Онлайн" const beforeRegBillSIR = async prms => { try { + //Код круппы ККТ + const sGroupCode = "v4-online-atol-ru_4179"; + //Токен доступа + let sToken = null; + if (prms.service.context && prms.service.context.sToken) { + sToken = prms.service.context.sToken; + } else { + sToken = await connect(prms); + } + + if (!sToken) throw Error("Не удалось получить токен доступа"); + //Разберем XML-данные фискального документа const parseRes = await parseXML(prms.queue.blMsg.toString()); + //Сохраним короткие ссылки на документ и его свойства const doc = parseRes.FISCDOC; const docProps = parseRes.FISCDOC.FISCDOC_PROPS.FISCDOC_PROP; + //Определим тип операции + const sOperation = mapDictionary(paymensOperation, getPropValueByCode(docProps, "1054")); + //Собираем тело запроса в JSON из XML-данных документа let reqBody = { timestamp: doc.SDDOC_DATE, external_id: doc.NRN, @@ -140,16 +290,17 @@ const beforeRegBillSIR = async prms => { callback_url: "" }, */ + receipt: { client: { - email: getPropValueByCode(docProps, "1008"), + email: "mim_@mail.ru", //getPropValueByCode(docProps, "1008"), phone: "" }, company: { - email: getPropValueByCode(docProps, "1117"), - sno: getPropValueByCode(docProps, "1117"), + email: "mim_@mail.ru", //getPropValueByCode(docProps, "1117"), + sno: "osn", //getPropValueByCode(docProps, "1055"), inn: getPropValueByCode(docProps, "1018"), - payment_address: getPropValueByCode(docProps, "1187") + payment_address: "г. Казань" //getPropValueByCode(docProps, "1187") }, /* agent_info: { @@ -179,10 +330,12 @@ const beforeRegBillSIR = async prms => { quantity: getPropValueByCode(docProps, "1023", "NUM"), sum: getPropValueByCode(docProps, "1043", "NUM"), measurement_unit: getPropValueByCode(docProps, "1197"), + //payment_method: "full_prepayment", + //payment_object: "service", payment_method: mapDictionary(paymentMethod, getPropValueByCode(docProps, "1214")), payment_object: mapDictionary(paymentObject, getPropValueByCode(docProps, "1212")), vat: { - type: "none", + type: "none", //getPropValueByCode(docProps, "1199") sum: getPropValueByCode(docProps, "1200", "NUM") } /*, agent_info: { @@ -211,36 +364,129 @@ const beforeRegBillSIR = async prms => { */ } ], - payments: [ - { - type: 0, - sum: 0 - } - ], - vats: [ - { - type: "", - sum: 0 - } - ], - total: 0 /*, - additional_check_props: "", - cashier: "", - additional_user_props: { - name: "", - value: "" - } - */ + total: getPropValueByCode(docProps, "1020", "NUM") } }; - console.log(util.inspect(reqBody, false, null)); + //Добавим общие платежи + let payments = []; + //Сумма по чеку электронными + if (getPropValueByCode(docProps, "1081", "NUM") !== null) { + payments.push({ + type: 1, + sum: getPropValueByCode(docProps, "1081", "NUM") + }); + } + //Сумма по чеку предоплатой (зачет аванса и (или) предыдущих платежей) + if (getPropValueByCode(docProps, "1215", "NUM") !== null) { + payments.push({ + type: 2, + sum: getPropValueByCode(docProps, "1215", "NUM") + }); + } + //Сумма по чеку постоплатой (кредит) + if (getPropValueByCode(docProps, "1216", "NUM") !== null) { + payments.push({ + type: 3, + sum: getPropValueByCode(docProps, "1216", "NUM") + }); + } + //Сумма по чеку встречным представлением + if (getPropValueByCode(docProps, "1217", "NUM") !== null) { + payments.push({ + type: 4, + sum: getPropValueByCode(docProps, "1217", "NUM") + }); + } + //Если есть хоть один платёж - помещаем массив в запрос + if (payments.length > 0) reqBody.receipt.payments = payments; + //Добавим общие налоги + let vats = []; + //Сумма расчета по чеку без НДС; + if (getPropValueByCode(docProps, "1105", "NUM") !== null) { + vats.push({ + type: "none", + sum: getPropValueByCode(docProps, "1105", "NUM") + }); + } + //Сумма расчета по чеку с НДС по ставке 0%; + if (getPropValueByCode(docProps, "1104", "NUM") !== null) { + vats.push({ + type: "vat0", + sum: getPropValueByCode(docProps, "1104", "NUM") + }); + } + //Сумма НДС чека по ставке 10%; + if (getPropValueByCode(docProps, "1103", "NUM") !== null) { + vats.push({ + type: "vat10", + sum: getPropValueByCode(docProps, "1103", "NUM") + }); + } + //Сумма НДС чека по ставке 18%; + if (getPropValueByCode(docProps, "1102", "NUM") !== null) { + vats.push({ + type: "vat18", + sum: getPropValueByCode(docProps, "1102", "NUM") + }); + } + //Сумма НДС чека по расч. ставке 10/110; + if (getPropValueByCode(docProps, "1107", "NUM") !== null) { + vats.push({ + type: "vat110", + sum: getPropValueByCode(docProps, "1107", "NUM") + }); + } + //Сумма НДС чека по расч. ставке 18/118 + if (getPropValueByCode(docProps, "1106", "NUM") !== null) { + vats.push({ + type: "vat118", + sum: getPropValueByCode(docProps, "1106", "NUM") + }); + } + //Если есть хоть один налог - помещаем массив в запрос + if (vats.length > 0) reqBody.receipt.vats = vats; + //Собираем общий результат работы + let res = { + options: { + method: prms.function.sFnPrmsType, + url: buildURL({ sSrvRoot: prms.service.sSrvRoot, sFnURL: prms.function.sFnURL }) + .replace("", sGroupCode) + .replace("", sOperation), + headers: { + "Content-type": "application/json; charset=utf-8", + Token: sToken + }, + simple: false + }, + blMsg: new Buffer(JSON.stringify(reqBody)), + context: { sToken } + }; + //Выводим что получилось + console.log(util.inspect(res, false, null)); + //Возврат резульатата + return res; } catch (e) { throw Error(e); } }; //Обработчик "После" отправки запроса на регистрацию чека (приход, расход, возврат) серверу "АТОЛ-Онлайн" -const afterRegBillSIR = async prms => {}; +const afterRegBillSIR = async prms => { + let tmp = null; + try { + tmp = JSON.parse(prms.serverResp); + } catch (e) { + throw Error("Неожиданный ответ сервера АТОЛ-Онлайн!"); + } + if (tmp.error !== null) { + throw Error(tmp.error.text); + } else { + console.log(tmp); + return { + blResp: new Buffer(tmp.uuid) + }; + } +}; //----------------- // Интерфейс модуля