Модуль обмена с АТОЛ-онлайн - API v4, ФФД 1.05 (первая бета успешно отправляющая данные и разбирающая ответ) С Новым Годом меня
This commit is contained in:
parent
cb6345f5c7
commit
43c946b16a
@ -21,11 +21,29 @@ create or replace package body UDO_PKG_EXS_ATOL as
|
|||||||
)
|
)
|
||||||
is
|
is
|
||||||
REXSQUEUE EXSQUEUE%rowtype; -- Çàïèñü ïîçèöèè î÷åðåäè
|
REXSQUEUE EXSQUEUE%rowtype; -- Çàïèñü ïîçèöèè î÷åðåäè
|
||||||
|
CTMP clob; -- Áóôåð äëÿ õðàíåíèÿ äàííûõ îòâåòà ñåðâåðà
|
||||||
begin
|
begin
|
||||||
/* Ñ÷èòàåì çàïèñü î÷åðåäè */
|
/* Ñ÷èòàåì çàïèñü î÷åðåäè */
|
||||||
REXSQUEUE := GET_EXSQUEUE_ID(NFLAG_SMART => 0, NRN => NEXSQUEUE);
|
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 V4_FFD105_PROCESS_REG_BILL_SIR;
|
||||||
|
|
||||||
end;
|
end;
|
||||||
|
@ -190,6 +190,9 @@ begin
|
|||||||
PKG_EXS.QUEUE_PUT(SEXSSERVICE => SEXSSERVICE,
|
PKG_EXS.QUEUE_PUT(SEXSSERVICE => SEXSSERVICE,
|
||||||
SEXSSERVICEFN => SEXSSERVICEFN,
|
SEXSSERVICEFN => SEXSSERVICEFN,
|
||||||
BMSG => CLOB2BLOB(LCDATA => CDATA, SCHARSET => 'UTF8'),
|
BMSG => CLOB2BLOB(LCDATA => CDATA, SCHARSET => 'UTF8'),
|
||||||
|
NLNK_COMPANY => NCOMPANY,
|
||||||
|
NLNK_DOCUMENT => NFISCDOC,
|
||||||
|
SLNK_UNITCODE => 'UDO_FiscalDocuments',
|
||||||
NNEW_EXSQUEUE => NEXSQUEUE);
|
NNEW_EXSQUEUE => NEXSQUEUE);
|
||||||
end;
|
end;
|
||||||
/
|
/
|
||||||
|
@ -1,6 +1,103 @@
|
|||||||
/*
|
/*
|
||||||
Сервис интеграции ПП Парус 8 с WEB API
|
Сервис интеграции ПП Парус 8 с WEB API
|
||||||
Дополнительный модуль: Взаимодействие с "АТОЛ-Онлайн" (v4) в формате ФФД 1.05
|
Дополнительный модуль: Взаимодействие с "АТОЛ-Онлайн" (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 util = require("util"); //Встроенные вспомогательные утилиты
|
||||||
const parseString = require("xml2js").parseString; //Конвертация XML в JSON
|
const parseString = require("xml2js").parseString; //Конвертация XML в JSON
|
||||||
const _ = require("lodash"); //Работа с массивами и коллекциями
|
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;
|
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 => {
|
const beforeRegBillSIR = async prms => {
|
||||||
try {
|
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 parseRes = await parseXML(prms.queue.blMsg.toString());
|
||||||
|
//Сохраним короткие ссылки на документ и его свойства
|
||||||
const doc = parseRes.FISCDOC;
|
const doc = parseRes.FISCDOC;
|
||||||
const docProps = parseRes.FISCDOC.FISCDOC_PROPS.FISCDOC_PROP;
|
const docProps = parseRes.FISCDOC.FISCDOC_PROPS.FISCDOC_PROP;
|
||||||
|
//Определим тип операции
|
||||||
|
const sOperation = mapDictionary(paymensOperation, getPropValueByCode(docProps, "1054"));
|
||||||
|
//Собираем тело запроса в JSON из XML-данных документа
|
||||||
let reqBody = {
|
let reqBody = {
|
||||||
timestamp: doc.SDDOC_DATE,
|
timestamp: doc.SDDOC_DATE,
|
||||||
external_id: doc.NRN,
|
external_id: doc.NRN,
|
||||||
@ -140,16 +290,17 @@ const beforeRegBillSIR = async prms => {
|
|||||||
callback_url: ""
|
callback_url: ""
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
|
||||||
receipt: {
|
receipt: {
|
||||||
client: {
|
client: {
|
||||||
email: getPropValueByCode(docProps, "1008"),
|
email: "mim_@mail.ru", //getPropValueByCode(docProps, "1008"),
|
||||||
phone: ""
|
phone: ""
|
||||||
},
|
},
|
||||||
company: {
|
company: {
|
||||||
email: getPropValueByCode(docProps, "1117"),
|
email: "mim_@mail.ru", //getPropValueByCode(docProps, "1117"),
|
||||||
sno: getPropValueByCode(docProps, "1117"),
|
sno: "osn", //getPropValueByCode(docProps, "1055"),
|
||||||
inn: getPropValueByCode(docProps, "1018"),
|
inn: getPropValueByCode(docProps, "1018"),
|
||||||
payment_address: getPropValueByCode(docProps, "1187")
|
payment_address: "г. Казань" //getPropValueByCode(docProps, "1187")
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
agent_info: {
|
agent_info: {
|
||||||
@ -179,10 +330,12 @@ const beforeRegBillSIR = async prms => {
|
|||||||
quantity: getPropValueByCode(docProps, "1023", "NUM"),
|
quantity: getPropValueByCode(docProps, "1023", "NUM"),
|
||||||
sum: getPropValueByCode(docProps, "1043", "NUM"),
|
sum: getPropValueByCode(docProps, "1043", "NUM"),
|
||||||
measurement_unit: getPropValueByCode(docProps, "1197"),
|
measurement_unit: getPropValueByCode(docProps, "1197"),
|
||||||
|
//payment_method: "full_prepayment",
|
||||||
|
//payment_object: "service",
|
||||||
payment_method: mapDictionary(paymentMethod, getPropValueByCode(docProps, "1214")),
|
payment_method: mapDictionary(paymentMethod, getPropValueByCode(docProps, "1214")),
|
||||||
payment_object: mapDictionary(paymentObject, getPropValueByCode(docProps, "1212")),
|
payment_object: mapDictionary(paymentObject, getPropValueByCode(docProps, "1212")),
|
||||||
vat: {
|
vat: {
|
||||||
type: "none",
|
type: "none", //getPropValueByCode(docProps, "1199")
|
||||||
sum: getPropValueByCode(docProps, "1200", "NUM")
|
sum: getPropValueByCode(docProps, "1200", "NUM")
|
||||||
} /*,
|
} /*,
|
||||||
agent_info: {
|
agent_info: {
|
||||||
@ -211,36 +364,129 @@ const beforeRegBillSIR = async prms => {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
payments: [
|
total: getPropValueByCode(docProps, "1020", "NUM")
|
||||||
{
|
|
||||||
type: 0,
|
|
||||||
sum: 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
vats: [
|
|
||||||
{
|
|
||||||
type: "",
|
|
||||||
sum: 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
total: 0 /*,
|
|
||||||
additional_check_props: "",
|
|
||||||
cashier: "",
|
|
||||||
additional_user_props: {
|
|
||||||
name: "",
|
|
||||||
value: ""
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
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("<group_code>", sGroupCode)
|
||||||
|
.replace("<operation>", 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) {
|
} catch (e) {
|
||||||
throw Error(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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//-----------------
|
//-----------------
|
||||||
// Интерфейс модуля
|
// Интерфейс модуля
|
||||||
|
Loading…
x
Reference in New Issue
Block a user