Запрос статуса зарегистрированного в АТОЛ-Онлайн чека, канализация алгоритмов подбора функции и сервиса обработчика от версии ФФД

This commit is contained in:
Mikhail Chechnev 2019-01-11 01:39:00 +03:00
parent 2c6e24f91e
commit 9b42179bde
5 changed files with 571 additions and 57 deletions

View File

@ -1,5 +1,21 @@
create or replace package UDO_PKG_EXS_ATOL as
/* Константы - типы функций обработки */
SFN_TYPE_REG_BILL constant varchar2(20) := 'REG_BILL'; -- Типовая функция регистрации чека
SFN_TYPE_GET_BILL_INF constant varchar2(20) := 'GET_BILL_INF'; -- Типовая функция получения иформации о регистрации чека
/* Получение рег. номера функции сервиса обмена для регистрации чека по рег. номеру фискального документа */
function UTL_FISCDOC_GET_REG_EXSFN
(
NFISCDOC in number -- Рег. номер фискального документа
) return number; -- Рег. номер функции регистрации чека в сервисе АТОЛ-Онлайн
/* Получение рег. номера функции сервиса обмена для запроса информации о регистрации чека по рег. номеру фискального документа */
function UTL_FISCDOC_GET_INF_EXSFN
(
NFISCDOC in number -- Рег. номер фискального документа
) return number; -- Рег. номер функции запроса информации о регистрации чека в сервисе АТОЛ-Онлайн
/* Îòðàáîòêà îòâåòîâ ÀÒÎË (v4) íà ðåãèñòðàöèþ ÷åêà íà ïðèõîä, ðàñõîä, âîçâðàò (ÔÔÄ 1.05) */
procedure V4_FFD105_PROCESS_REG_BILL_SIR
(
@ -18,6 +34,180 @@ end;
/
create or replace package body UDO_PKG_EXS_ATOL as
/* Константы - состояния документа в АТОЛ */
SSTATUS_DONE constant varchar2(10) := 'done'; -- Готово
SSTATUS_FAIL constant varchar2(10) := 'fail'; -- Ошибка
SSTATUS_WAIT constant varchar2(10) := 'wait'; -- Ожидание
/* Шаблон URL для чека ОФД */
SBILL_OFD_UTL constant varchar2(80) := 'https://ofd.ru/rec/<1041>/<1040>/<1077>?format=pdf';
/* Проверка корректности атрибутов позиции очереди */
procedure UTL_EXSQUEUE_CHECK_ATTRS
(
REXSQUEUE in EXSQUEUE%rowtype -- Проверяемая запись позиции очереди
)
is
begin
/* Должна быть организация */
if (REXSQUEUE.LNK_COMPANY is null) then
P_EXCEPTION(0, 'Для позиции очереди не указана связанная организация.');
end if;
/* Должна быть связь с документом */
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;
end UTL_EXSQUEUE_CHECK_ATTRS;
/* Считывание записи фискального документа */
function UTL_FISCDOC_GET
(
NFISCDOC in number -- Рег. номер фискального документа
) return UDO_V_FISCDOCS%rowtype -- Найденная запись фискального документа
is
RRES UDO_V_FISCDOCS%rowtype; -- Буфер для результата
begin
/* Считаем запись */
select T.* into RRES from UDO_V_FISCDOCS T where T.NRN = NFISCDOC;
/* Вернём результат */
return RRES;
exception
when NO_DATA_FOUND then
PKG_MSG.RECORD_NOT_FOUND(NFLAG_SMART => 0, NDOCUMENT => NFISCDOC, SUNIT_TABLE => 'UDO_FISCDOCS');
end UTL_FISCDOC_GET;
/* Получение мнемокода сервиса обмена и мнемокода его функции по типу функции обработки и версии ФФД */
procedure UTL_FISCDOC_GET_EXSFN_BY_FFD
(
SFN_TYPE in varchar2, -- Тип функции обработки (см. константы SFN_TYPE_*)
SFFD_VERSION in varchar2, -- Версия ФФД
SEXSSERVICE out varchar2, -- Код сервиса-обработчика
SEXSSERVICEFN out varchar2 -- Код функции-обработчика
)
is
begin
/* Работаем от типовой функции */
case SFN_TYPE
/* Регистрация чека */
when SFN_TYPE_REG_BILL then
begin
/* Выбираем API обмена в зависимости от версии фискального документа */
case SFFD_VERSION
/* ФФД 1.05 */
when '1.05' then
begin
SEXSSERVICE := 'АТОЛ_V4_ИСХ';
SEXSSERVICEFN := 'V4_ФФД1.05_РегистрацияЧекаРПВ';
end;
/* Неизвестная версия ФФД */
else
begin
P_EXCEPTION(0,
'Версия фискального документа "%s" не поддерживается!',
SFFD_VERSION);
end;
end case;
end;
/* Получение иформации о регистрации чека */
when SFN_TYPE_GET_BILL_INF then
begin
/* Выбираем API обмена в зависимости от версии фискального документа */
case SFFD_VERSION
/* ФФД 1.05 */
when '1.05' then
begin
SEXSSERVICE := 'АТОЛ_V4_ИСХ';
SEXSSERVICEFN := 'V4_ФФД1.05_РезОбрабЧека';
end;
/* Неизвестная версия ФФД */
else
begin
P_EXCEPTION(0,
'Версия фискального документа "%s" не поддерживается!',
SFFD_VERSION);
end;
end case;
end;
/* Неизвестная типовая функция */
else
begin
P_EXCEPTION(0, 'Типовая функция "%s" не поддерживается!', SFN_TYPE);
end;
end case;
end UTL_FISCDOC_GET_EXSFN_BY_FFD;
/* Получение рег. номера функции сервиса обмена для регистрации чека по рег. номеру фискального документа */
function UTL_FISCDOC_GET_REG_EXSFN
(
NFISCDOC in number -- Рег. номер фискального документа
) return number -- Рег. номер функции регистрации чека в сервисе АТОЛ-Онлайн
is
NRES PKG_STD.TREF; -- Буфер для результата
RFISCDOC UDO_V_FISCDOCS%rowtype; -- Запись фискального документа
NEXSSERVICE EXSSERVICEFN.RN%type; -- Рег. номер сервиса-обработчика
SEXSSERVICE EXSSERVICEFN.CODE%type; -- Код сервиса-обработчика
SEXSSERVICEFN EXSSERVICEFN.CODE%type; -- Код функции-обработчика
begin
/* Считаем запись фискального документа */
RFISCDOC := UTL_FISCDOC_GET(NFISCDOC => NFISCDOC);
/* Определим мнемокоды сервиса и функции для обработки */
UTL_FISCDOC_GET_EXSFN_BY_FFD(SFN_TYPE => SFN_TYPE_REG_BILL,
SFFD_VERSION => RFISCDOC.STYPE_VERSION,
SEXSSERVICE => SEXSSERVICE,
SEXSSERVICEFN => SEXSSERVICEFN);
/* Находим рег. номер сервиса */
FIND_EXSSERVICE_CODE(NFLAG_SMART => 0, NFLAG_OPTION => 0, SCODE => SEXSSERVICE, NRN => NEXSSERVICE);
/* Находим рег. номер функции сервиса */
FIND_EXSSERVICEFN_CODE(NFLAG_SMART => 0,
NFLAG_OPTION => 0,
NEXSSERVICE => NEXSSERVICE,
SCODE => SEXSSERVICEFN,
NRN => NRES);
/* Вернём результат */
return NRES;
end UTL_FISCDOC_GET_REG_EXSFN;
/* Получение рег. номера функции сервиса обмена для запроса информации о регистрации чека по рег. номеру фискального документа */
function UTL_FISCDOC_GET_INF_EXSFN
(
NFISCDOC in number -- Рег. номер фискального документа
) return number -- Рег. номер функции запроса информации о регистрации чека в сервисе АТОЛ-Онлайн
is
NRES PKG_STD.TREF; -- Буфер для результата
RFISCDOC UDO_V_FISCDOCS%rowtype; -- Запись фискального документа
NEXSSERVICE EXSSERVICEFN.RN%type; -- Рег. номер сервиса-обработчика
SEXSSERVICE EXSSERVICEFN.CODE%type; -- Код сервиса-обработчика
SEXSSERVICEFN EXSSERVICEFN.CODE%type; -- Код функции-обработчика
begin
/* Считаем запись фискального документа */
RFISCDOC := UTL_FISCDOC_GET(NFISCDOC => NFISCDOC);
/* Определим мнемокоды сервиса и функции для обработки */
UTL_FISCDOC_GET_EXSFN_BY_FFD(SFN_TYPE => SFN_TYPE_GET_BILL_INF,
SFFD_VERSION => RFISCDOC.STYPE_VERSION,
SEXSSERVICE => SEXSSERVICE,
SEXSSERVICEFN => SEXSSERVICEFN);
/* Находим рег. номер сервиса */
FIND_EXSSERVICE_CODE(NFLAG_SMART => 0, NFLAG_OPTION => 0, SCODE => SEXSSERVICE, NRN => NEXSSERVICE);
/* Находим рег. номер функции сервиса */
FIND_EXSSERVICEFN_CODE(NFLAG_SMART => 0,
NFLAG_OPTION => 0,
NEXSSERVICE => NEXSSERVICE,
SCODE => SEXSSERVICEFN,
NRN => NRES);
/* Вернём результат */
return NRES;
end UTL_FISCDOC_GET_INF_EXSFN;
/* Îòðàáîòêà îòâåòîâ ÀÒÎË (v4) íà ðåãèñòðàöèþ ÷åêà íà ïðèõîä, ðàñõîä, âîçâðàò (ÔÔÄ 1.05) */
procedure V4_FFD105_PROCESS_REG_BILL_SIR
(
@ -26,21 +216,20 @@ create or replace package body UDO_PKG_EXS_ATOL as
)
is
REXSQUEUE EXSQUEUE%rowtype; -- Çàïèñü ïîçèöèè î÷åðåäè
RFISCDOC UDO_V_FISCDOCS%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
UTL_EXSQUEUE_CHECK_ATTRS(REXSQUEUE => REXSQUEUE);
/* Считаем запись фискального документа */
RFISCDOC := UTL_FISCDOC_GET(NFISCDOC => REXSQUEUE.LNK_DOCUMENT);
/* Проверим, что он верного формата */
if (RFISCDOC.STYPE_VERSION <> '1.05') then
P_EXCEPTION(0,
'Ñâÿçàííûé ðàçäåë "%s", óêàçàííûé â ïîçèöèè î÷åðåäè, íå ïîääåðæèâàåòñÿ.',
REXSQUEUE.LNK_UNITCODE);
'Версия формата фискального документа (%s) не поддерживается. Ожидаемая версия - 1.05.',
RFISCDOC.STYPE_VERSION);
end if;
/* Ðàçáèðàåì îòâåò */
CTMP := BLOB2CLOB(LBDATA => REXSQUEUE.RESP, SCHARSET => 'UTF8');
@ -65,15 +254,148 @@ create or replace package body UDO_PKG_EXS_ATOL as
)
is
REXSQUEUE EXSQUEUE%rowtype; -- Çàïèñü ïîçèöèè î÷åðåäè
CTMP clob; -- Áóôåð äëÿ õðàíåíèÿ äàííûõ îòâåòà ñåðâåðà
RFISCDOC UDO_V_FISCDOCS%rowtype; -- Запись фискального документа
RDOC PKG_XPATH.TDOCUMENT; -- Разобранный XML-документ
RROOT_NODE PKG_XPATH.TNODE; -- Корневой тэг XML-документа
SSTATUS PKG_STD.TSTRING; -- Буфер для значения "Статус обработки документа"
STIMESTAMP PKG_STD.TSTRING; -- Буфер для значения "Дата и время документа внешней системы" (строковое представление)
DTIMESTAMP PKG_STD.TLDATE; -- Буфер для значения "Дата и время документа внешней системы"
STAG1012 PKG_STD.TSTRING; -- Буфер для значения "Дата и время документа из ФН" (тэг 1012)
STAG1040 PKG_STD.TSTRING; -- Буфер для значения "Фискальный номер документа" (тэг 1040)
STAG1041 PKG_STD.TSTRING; -- Буфер для значения "Номер ФН" (тэг 1041)
STAG1077 PKG_STD.TSTRING; -- Буфер для значения "Фискальный признак документа" (тэг 1077)
SERR_CODE PKG_STD.TSTRING; -- Буфер для значения "Код ошибки"
SERR_TEXT PKG_STD.TSTRING; -- Буфер для значения "Текст ошибки"
begin
/* Ñ÷èòàåì çàïèñü î÷åðåäè */
REXSQUEUE := GET_EXSQUEUE_ID(NFLAG_SMART => 0, NRN => NEXSQUEUE);
/* Ðàçáèðàåì îòâåò */
CTMP := BLOB2CLOB(LBDATA => REXSQUEUE.RESP, SCHARSET => 'UTF8');
if (CTMP is null) then
P_EXCEPTION(0, 'Íåò îòâåòà îò ñåðâåðà.');
/* Проверим что позиция очереди корректна */
UTL_EXSQUEUE_CHECK_ATTRS(REXSQUEUE => REXSQUEUE);
/* Считаем запись фискального документа */
RFISCDOC := UTL_FISCDOC_GET(NFISCDOC => REXSQUEUE.LNK_DOCUMENT);
/* Проверим, что он верного формата */
if (RFISCDOC.STYPE_VERSION <> '1.05') then
P_EXCEPTION(0,
'Версия формата фискального документа (%s) не поддерживается. Ожидаемая версия - 1.05.',
RFISCDOC.STYPE_VERSION);
end if;
/* Разбираем ответ */
begin
RDOC := PKG_XPATH.PARSE_FROM_BLOB(LBXML => REXSQUEUE.RESP, SCHARSET => 'UTF8');
exception
when others then
P_EXCEPTION(0, 'Ошибка разбора XML - неожиданный ответ сервера.');
end;
/* Находим корневой элемент */
RROOT_NODE := PKG_XPATH.ROOT_NODE(RDOCUMENT => RDOC);
/* Забираем значения документа */
SSTATUS := PKG_XPATH.VALUE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RROOT_NODE, SPATTERN => '/RESP/STATUS'));
STIMESTAMP := PKG_XPATH.VALUE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RROOT_NODE,
SPATTERN => '/RESP/TIMESTAMP'));
STAG1012 := PKG_XPATH.VALUE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RROOT_NODE, SPATTERN => '/RESP/TAG1012'));
STAG1040 := PKG_XPATH.VALUE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RROOT_NODE, SPATTERN => '/RESP/TAG1040'));
STAG1041 := PKG_XPATH.VALUE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RROOT_NODE, SPATTERN => '/RESP/TAG1041'));
STAG1077 := PKG_XPATH.VALUE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RROOT_NODE, SPATTERN => '/RESP/TAG1077'));
SERR_CODE := PKG_XPATH.VALUE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RROOT_NODE,
SPATTERN => '/RESP/ERROR/CODE'));
SERR_TEXT := PKG_XPATH.VALUE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RROOT_NODE,
SPATTERN => '/RESP/ERROR/TEXT'));
/* Освобождаем память */
PKG_XPATH.FREE(RDOCUMENT => RDOC);
/* Проверим, что указан статус документа */
if (SSTATUS is null) then
P_EXCEPTION(0, 'Не указан статус обработки документа.');
end if;
/* Обрабатываем ответ в зависимости от статуса */
case SSTATUS
/* Обрабатывается */
when SSTATUS_WAIT then
begin
/* Документ ещё не обработан, ожидаем результатов, поэтому пока ничего не делаем */
null;
end;
/* Готов */
when SSTATUS_DONE then
begin
/* Проверим наличие данных в тэгах и дату ответа АТОЛ */
if (STIMESTAMP is null) then
P_EXCEPTION(0,
'Документ в статусе "%s", но не указано значение "Дата и время документа внешней системы".',
SSTATUS);
end if;
if (STAG1040 is null) then
P_EXCEPTION(0,
'Документ в статусе "%s", но не указано значение "Фискальный номер документа" (тэг 1040).',
SSTATUS);
end if;
if (STAG1041 is null) then
P_EXCEPTION(0,
'Документ в статусе "%s", но не указано значение "Номер ФН" (тэг 1041).',
SSTATUS);
end if;
if (STAG1077 is null) then
P_EXCEPTION(0,
'Документ в статусе "%s", но не указано значение "Фискальный признак документа" (тэг 1077).',
SSTATUS);
end if;
/* Проверяем корректность даты подтверждения */
begin
DTIMESTAMP := TO_DATE(STIMESTAMP, 'dd.mm.yyyy hh24:mi:ss');
exception
when others then
P_EXCEPTION(0,
'Значение поля "Дата и время документа внешней системы" (%s) не является датой в формате "ДД.ММ.ГГГГ ЧЧ:МИ:CC"',
STIMESTAMP);
end;
/* Выставляем значение "Дата подтверждения" и "Ссылка на фискальный документ в ОФД" для фискального документа */
update UDO_FISCDOCS T
set T.CONFIRM_DATE = DTIMESTAMP,
T.DOC_URL = replace(replace(replace(SBILL_OFD_UTL, '<1040>', STAG1040), '<1041>', STAG1041),
'<1077>',
STAG1077)
where T.RN = RFISCDOC.NRN;
/* Устанавливаем значения тэгов */
begin
UDO_P_FISCDOCSPROP_SET_VAL(NPRN => RFISCDOC.NRN,
NCOMPANY => RFISCDOC.NCOMPANY,
SATTRIBUTE => '1040',
NVAL_NUMB => TO_NUMBER(STAG1040));
UDO_P_FISCDOCSPROP_SET_VAL(NPRN => RFISCDOC.NRN,
NCOMPANY => RFISCDOC.NCOMPANY,
SATTRIBUTE => '1041',
SVAL_STR => STAG1041);
UDO_P_FISCDOCSPROP_SET_VAL(NPRN => RFISCDOC.NRN,
NCOMPANY => RFISCDOC.NCOMPANY,
SATTRIBUTE => '1077',
SVAL_STR => STAG1077);
exception
when others then
P_EXCEPTION(0,
'Ошибка установки значения атрибута фискального документа: %s',
sqlerrm);
end;
end;
/* Ошибка обработки */
when SSTATUS_FAIL then
begin
/* Проверим, что пришли код и текст ошибки */
if ((SERR_CODE is null) or (SERR_TEXT is null)) then
P_EXCEPTION(0,
'Документ в статусе "%s", но не указан код или текст ошибки.',
SSTATUS);
end if;
/* Выставим код и текст в фискальном документе */
update UDO_FISCDOCS T
set T.SEND_ERROR = SUBSTR(SERR_CODE || ': ' ||
REGEXP_REPLACE(replace(SERR_TEXT, CHR(10), ''), '[[:space:]]+', ' '),
1,
4000)
where T.RN = RFISCDOC.NRN;
end;
/* Неизвестный статус */
else
P_EXCEPTION(0, 'Cтатус докуента "%s" не поддерживается.', SSTATUS);
end case;
/* Âñ¸ ïðîøëî óñïåøíî */
PKG_EXS.PRC_RESP_RESULT_SET(NIDENT => NIDENT);
exception
@ -81,5 +403,6 @@ create or replace package body UDO_PKG_EXS_ATOL as
/* Âåðí¸ì îøèáêó */
PKG_EXS.PRC_RESP_RESULT_SET(NIDENT => NIDENT, SRESULT => PKG_EXS.SPRC_RESP_RESULT_ERR, SMSG => sqlerrm);
end V4_FFD105_PROCESS_GET_BILL_INF;
end;
/

View File

@ -0,0 +1,71 @@
create or replace procedure UDO_P_FISCDOCS_BASE_SEND
(
NRN in number, -- Ðåãèñòðàöèîííûé íîìåð
NCOMPANY in number -- Îðãàíèçàöèÿ
)
as
NEXSQUEUE PKG_STD.TREF; -- Ðåãèñòðàöèîííûé íîìåð çàïèñè î÷åðåäè îáìåíà
SPRC_NAME varchar2(60); -- Íàèìåíîâàíèå ïðîöåäóðû ïðîâåðêè çíà÷åíèé
PRMS PKG_CONTPRMLOC.TCONTAINER; -- Êîíòåéíåð äëÿ ïàðàìåòðîâ ïðîöåäóðû îáðàáîòêè
begin
/* Ïðîâåðêà çàïîëíåíèÿ îáÿçàòåëüíûõ àòðèáóòîâ */
UDO_P_FISCDOCSPROP_CHECK_REQ(NCOMPANY => NCOMPANY, NPRN => NRN);
/* Îïðåäåëåíèå ïðîöåäóðû ïðîâåðêè çíà÷åíèé */
for REC in (select V.PKG_CHECK,
V.PRC_CHECK
from UDO_FISCDOCS T,
UDO_FDKNDVERS V
where T.RN = NRN
and T.COMPANY = NCOMPANY
and T.TYPE_VERSION = V.RN)
loop
/* Íàèìåíîâàíèå ïðîöåäóðû */
SPRC_NAME := NULLIF(REC.PKG_CHECK || '.', '.') || REC.PRC_CHECK;
end loop;
/* Åñëè åñòü çàïîëíåíà ïðîöåäóðà ïðîâåðêè */
if (SPRC_NAME is not null) then
/* Óñòàíîâêà çíà÷åíèé ôèêñèðîâàííûõ âõîäíûõ ïàðàìåòðîâ */
PKG_CONTPRMLOC.APPENDN(RCONTAINER => PRMS, SNAME => 'NRN', NVALUE => NRN, NIN_OUT => PKG_STD.IPARAM_TYPE_IN);
PKG_CONTPRMLOC.APPENDN(RCONTAINER => PRMS,
SNAME => 'NCOMPANY',
NVALUE => NCOMPANY,
NIN_OUT => PKG_STD.IPARAM_TYPE_IN);
/* Âûïîëíåíèå ïðîöåäóðû */
begin
PKG_SQL_CALL.EXECUTE_STORED(SSTORED_NAME => SPRC_NAME, RPARAM_CONTAINER => PRMS);
exception
when others then
P_EXCEPTION(0,
'Îøèáêà âûïîëíåíèÿ ïðîöåäóðû "%s".' || CR || 'Òåêñò îøèáêè: %s',
SPRC_NAME,
sqlerrm);
end;
end if;
/* Ôîðìèðîâàíèå è îòïðàâêà ñîîáùåíèÿ äëÿ ÀÒÎË-Îíëàéí */
UDO_P_FISCDOCS_MAKE_MSG_ATOL(NCOMPANY => NCOMPANY, NFISCDOC => NRN, NEXSQUEUE => NEXSQUEUE);
/* Ñîçäàíèå ñâÿçè ìåæäó ÷åêîì è î÷åðåäüþ îáìåíà */
PKG_DOCLINKS.LINK(NFLAG_SMART => 0,
NCOMPANY => NCOMPANY,
SIN_UNITCODE => 'UDO_FiscalDocuments',
NIN_DOCUMENT => NRN,
SOUT_UNITCODE => 'EXSQueue',
NOUT_DOCUMENT => NEXSQUEUE);
/* Èçìåíåíèå ñòàòóñà */
UDO_P_FISCDOCS_BASE_SET_STATUS(NRN => NRN, NCOMPANY => NCOMPANY, NSTATUS => 1);
/* Óñòàíîâêà äàòû è âðåìåíè îòïðàâêè */
update UDO_FISCDOCS
set SEND_TIME = sysdate
where RN = NRN
and COMPANY = NCOMPANY;
if (sql%notfound) then
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'UDO_FiscalDocuments');
end if;
end;
/

View File

@ -0,0 +1,27 @@
create or replace procedure UDO_P_FISCDOCS_GET_STATE_ATOL
as
NEXSQUEUE PKG_STD.TREF; -- Ðåãèñòðàöèîííûé íîìåð äîáàâëåííîé ïîçèöèè î÷åðåäè îáìåíà
begin
/* Îáõîäèì ôèñêàëüíûå äîêóìåíòû, óñïåøíî îòïðàâëåííûå â ÀÒÎË, íî ïî êîòîðûì åù¸ íåò ïðîñòàâëåííîãî ñòàòóñà */
for C in (select T.COMPANY,
T.RN,
Q.RESP BUUID
from UDO_FISCDOCS T,
EXSQUEUE Q
where ((T.SEND_ERROR is null) and (T.CONFIRM_DATE is null))
and Q.LNK_COMPANY = T.COMPANY
and Q.LNK_UNITCODE = 'UDO_FiscalDocuments'
and Q.LNK_DOCUMENT = T.RN
and Q.EXEC_STATE = PKG_EXS.NQUEUE_EXEC_STATE_OK
and Q.EXSSERVICEFN = UDO_PKG_EXS_ATOL.UTL_FISCDOC_GET_REG_EXSFN(T.RN))
loop
/* Ñòàâèì çàïðîñ íà ïîëó÷åíèå ñòàòóñà äîêóìåíòà â î÷åðåäü */
PKG_EXS.QUEUE_PUT(NEXSSERVICEFN => UDO_PKG_EXS_ATOL.UTL_FISCDOC_GET_INF_EXSFN(NFISCDOC => C.RN),
BMSG => C.BUUID,
NLNK_COMPANY => C.COMPANY,
NLNK_DOCUMENT => C.RN,
SLNK_UNITCODE => 'UDO_FiscalDocuments',
NNEW_EXSQUEUE => NEXSQUEUE);
end loop;
end;
/

View File

@ -7,8 +7,6 @@ create or replace procedure UDO_P_FISCDOCS_MAKE_MSG_ATOL
as
/* Ëîêàëüíûå ïåðåìåííûå */
CDATA clob; -- Áóôåð äëÿ XML-ïîñûëêè
SEXSSERVICE EXSSERVICEFN.CODE%type; -- Êîä ñåðâèñà-îáðàáîò÷èêà
SEXSSERVICEFN EXSSERVICEFN.CODE%type; -- Êîä ôóíêöèè îòïðàâêè
NTMP_RN PKG_STD.TREF; -- Áóôåð äëÿ âû÷èñëåíèé
/* Äîáàâëåíèå ïóñòîé îòêðûòîé âåòêè */
@ -28,7 +26,7 @@ as
procedure NODE
(
SNAME varchar2, -- Èìÿ âåòêè
SVALUE varchar2 -- Çíà÷åíèå âåòêè
SVALUE varchar2 -- Çíà÷åíèå âåòêè (ñòðîêà)
)
as
begin
@ -46,7 +44,7 @@ as
procedure NODE
(
SNAME varchar2, -- Èìÿ âåòêè
NVALUE number -- Çíà÷åíèå âåòêè
NVALUE number -- Çíà÷åíèå âåòêè (÷èñëî)
)
as
begin
@ -64,7 +62,7 @@ as
procedure NODE
(
SNAME varchar2, -- Èìÿ âåòêè
DVALUE date -- Çíà÷åíèå âåòêè
DVALUE date -- Çíà÷åíèå âåòêè (äàòà)
)
as
begin
@ -93,22 +91,6 @@ begin
where T.NRN = UDO_P_FISCDOCS_MAKE_MSG_ATOL.NFISCDOC
and T.NCOMPANY = UDO_P_FISCDOCS_MAKE_MSG_ATOL.NCOMPANY)
loop
/* Âûáèðàåì API îáìåíà â çàâèñèìîñòè îò âåðñèè ôèñêàëüíîãî äîêóìåíòà */
case D.STYPE_VERSION
/* ÔÔÄ 1.05 */
when '1.05' then
begin
SEXSSERVICE := 'ÀÒÎË_V4_ÈÑÕ';
SEXSSERVICEFN := 'V4_ÔÔÄ1.05_Ðåãèñòðàöèÿ×åêàÐÏÂ';
end;
/* Íåèçâåñòíàÿ âåðñèÿ ÔÔÄ */
else
begin
P_EXCEPTION(0,
'Âåðñèÿ ôèñêàëüíîãî äîêóìåíòà "%s" íå ïîääåðæèâàåòñÿ!',
D.STYPE_VERSION);
end;
end case;
/* Äàííûå çàãîëîâêà ôèñêàëüíîãî äîêóìåíòà */
NODE(SNAME => 'NRN', NVALUE => D.NRN);
NODE(SNAME => 'NCOMPANY', NVALUE => D.NCOMPANY);
@ -187,8 +169,7 @@ begin
/* Ôèíàëèçèðóåì ñáîðêó XML-äîêóìåíòà */
PKG_XMLFAST.EPILOGUE(LDATA => CDATA);
/* Îòïðàâëÿåì ñôîðìèðîâàííûé äîêóìåíò */
PKG_EXS.QUEUE_PUT(SEXSSERVICE => SEXSSERVICE,
SEXSSERVICEFN => SEXSSERVICEFN,
PKG_EXS.QUEUE_PUT(NEXSSERVICEFN => UDO_PKG_EXS_ATOL.UTL_FISCDOC_GET_REG_EXSFN(NFISCDOC => NFISCDOC),
BMSG => CLOB2BLOB(LCDATA => CDATA, SCHARSET => 'UTF8'),
NLNK_COMPANY => NCOMPANY,
NLNK_DOCUMENT => NFISCDOC,

View File

@ -104,12 +104,10 @@
// Подключение библиотек
//----------------------
const util = require("util"); //Встроенные вспомогательные утилиты
const parseString = require("xml2js").parseString; //Конвертация XML в JSON
const js2xmlparser = require("js2xmlparser"); //Конвертация JSON в XML
const _ = require("lodash"); //Работа с массивами и коллекциями
const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами
const { buildURL } = require("@core/utils"); //Вспомогательные функции
const { NFN_TYPE_LOGIN } = require("@models/obj_service_function");
//---------------------
// Глобальные константы
@ -118,6 +116,11 @@ const { NFN_TYPE_LOGIN } = require("@models/obj_service_function");
//Код круппы ККТ
const SGROUP_CODE = "v4-online-atol-ru_4179";
//Статусы документов АТОЛ-онлайн
const SSTATUS_DONE = "done"; //Готово
const SSTATUS_FAIL = "fail"; //Ошибка
const SSTATUS_WAIT = "wait"; //Ожидание
//Словарь - Признак способа расчёта
const paymentMethod = {
sName: "Признак способа расчёта",
@ -474,24 +477,34 @@ const beforeRegBillSIR = async prms => {
//Обработчик "После" отправки запроса на регистрацию чека (приход, расход, возврат) серверу "АТОЛ-Онлайн"
const afterRegBillSIR = async prms => {
//Буфер для данных ответа сервера
let resp = null;
//Если есть данные от сервера АТОЛ
if (prms.queue.blResp) {
//Пытаемся их разбирать
try {
resp = JSON.parse(prms.queue.blResp.toString());
} catch (e) {
//Пришел не JSON
throw new Error(`Неожиданный ответ сервера АТОЛ-Онлайн. Ошибка интерпретации: ${e.message}`);
}
} else {
//Данных от сервера нет
throw new Error(`Сервер АТОЛ-Онлайн не вернул ответ`);
}
//Данные есть и нам удалось их разобрать, проверяем на наличие ошибок
if (resp.error === null) {
//Ошибок нет - забираем идентификатор документа в системе АТОЛ и кладём в тело ответа - он это то что нам нужно
return {
blResp: new Buffer(resp.uuid)
};
} else {
//Есть ошибки, посмотрим что это, может быть аутентификация (кончился токен)
if (resp.error.code === 10 || resp.error.code === 11) {
//да, это была она - сигнализируем серверу приложений - надо переподключаться
return { bUnAuth: true };
} else {
//прочие ошибки - фиксируем в журнале
throw new Error(`Сервер АТОЛ-Онлайн вернул ошибку: ${resp.error.text}`);
}
}
@ -529,8 +542,107 @@ const beforeGetBillInfo = async prms => {
//Обработчик "После" отправки запроса на получение информации о чеке серверу "АТОЛ-Онлайн"
const afterGetBillInfo = async prms => {
if (prms.queue.blResp) console.log(prms.queue.blResp.toString());
else console.log("Сервер не вернул ответ");
//if (prms.queue.blResp) console.log(prms.queue.blResp.toString());
//else console.log("Сервер не вернул ответ");
//Буфер для результата работы обработчика
let res = null;
//Буфер для данных ответа сервера
let resp = null;
//Если есть данные от сервера АТОЛ
if (prms.queue.blResp) {
//Пытаемся их разбирать
try {
resp = JSON.parse(prms.queue.blResp.toString());
} catch (e) {
//Пришел не JSON
throw new Error(`Неожиданный ответ сервера АТОЛ-Онлайн. Ошибка интерпретации: ${e.message}`);
}
} else {
//Данных от сервера нет
throw new Error(`Сервер АТОЛ-Онлайн не вернул ответ`);
}
//Данные есть и нам удалось их разобрать - проверим, что нет ошибок аутентификации
//Есть ошибки, посмотрим что это, может быть аутентификация (кончился токен)
if (resp.error !== null && (resp.error.code === 10 || resp.error.code === 11)) {
//да, это была она - сигнализируем серверу приложений - надо переподключаться и дальше не работаем
return { bUnAuth: true };
}
//Ошибок атуентификации нет - проверяем состояние документа
if (resp.status) {
//Проверям, может быть документ зарегистрирован
if (resp.status === SSTATUS_DONE) {
//Документ обработан, проверим наличие данных фискализации
if (resp.payload) {
//Ошибок нет - забираем данные фискализации и кладём в тело ответа - это то что нам нужно
res = {
//Статус обработки документа
STATUS: resp.status,
//Дата и время документа внешней системы
TIMESTAMP: resp.timestamp,
//Дата и время документа из ФН
TAG1012: resp.payload.receipt_datetime,
//Фискальный номер документа
TAG1040: resp.payload.fiscal_document_number,
//Номер ФН
TAG1041: resp.payload.fn_number,
//Фискальный признак документа
TAG1077: resp.payload.fiscal_document_attribute
};
} else {
//В ответе сервера нет ни данных фискализации, при этом документ отработан - это неожиданный ответ
throw new Error(
`Неожиданный ответ сервера АТОЛ-Онлайн. Ошибка интерпретации: документ в статусе "Готов" но в ответе сервера нет данных фискализации`
);
}
} else {
//Документ не зарегистрирован, может быть ещё обрабатывается
if (resp.status === SSTATUS_WAIT) {
//Скажем и об этом
res = {
//Статус обработки документа
STATUS: resp.status
};
} else {
//Документ не готов и не обрабатывается - очевидно ошибка при регистрации
if (resp.status === SSTATUS_FAIL) {
if (resp.error) {
res = {
//Статус обработки документа
STATUS: resp.status,
//Ошибка
ERROR: {
//Код ошибки
CODE: resp.error.code,
//Текст ошибки
TEXT: resp.error.text
}
};
} else {
//Статус - ошибка, но ошибки нет, это неожиданный ответ
throw new Error(
`Неожиданный ответ сервера АТОЛ-Онлайн. Ошибка интерпретации: документ в статусе "Ошибка" но в ответе сервера нет об ошибке`
);
}
} else {
//Других статусов быть не должно - это неожиданный ответ
throw new Error(
`Неожиданный ответ сервера АТОЛ-Онлайн. Ошибка интерпретации: неизвестный статус документа - ${
resp.status
}`
);
}
}
}
} else {
//Нет данных о статусе документа - это неожиданный ответ
throw new Error(
`Неожиданный ответ сервера АТОЛ-Онлайн. Ошибка интерпретации: статус документа в ответе не определён`
);
}
//Вернём сформированный ответ
return {
blResp: new Buffer(js2xmlparser.parse("RESP", res))
};
};
//-----------------