diff --git a/db/UDO_PKG_EXS_ATOL.pck b/db/UDO_PKG_EXS_ATOL.pck index 255d433..fb1a59f 100644 --- a/db/UDO_PKG_EXS_ATOL.pck +++ b/db/UDO_PKG_EXS_ATOL.pck @@ -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 ( @@ -10,38 +26,211 @@ create or replace package UDO_PKG_EXS_ATOL as /* Отработка ответов АТОЛ (v4) на запрос сведений о зарегистрированном документе (ФФД 1.05) */ procedure V4_FFD105_PROCESS_GET_BILL_INF ( - NIDENT in number, -- Идентификатор процесса - NEXSQUEUE in number -- Регистрационный номер обрабатываемой позиции очереди обмена + NIDENT in number, -- Идентификатор процесса + NEXSQUEUE in number -- Регистрационный номер обрабатываемой позиции очереди обмена ); end; / create or replace package body UDO_PKG_EXS_ATOL as - /* Отработка ответов АТОЛ (v4) на регистрацию чека на приход, расход, возврат (ФФД 1.05) */ - procedure V4_FFD105_PROCESS_REG_BILL_SIR + /* Константы - состояния документа в АТОЛ */ + 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 ( - NIDENT in number, -- Идентификатор процесса - NEXSQUEUE in number -- Регистрационный номер обрабатываемой позиции очереди обмена + REXSQUEUE in EXSQUEUE%rowtype -- Проверяемая запись позиции очереди ) is - REXSQUEUE EXSQUEUE%rowtype; -- Запись позиции очереди - CTMP clob; -- Буфер для хранения данных ответа сервера begin - /* Считаем запись очереди */ - REXSQUEUE := GET_EXSQUEUE_ID(NFLAG_SMART => 0, NRN => NEXSQUEUE); - /* Проверим что позиция очереди корректна */ + /* Должна быть организация */ + 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 + ( + NIDENT in number, -- Идентификатор процесса + NEXSQUEUE in number -- Регистрационный номер обрабатываемой позиции очереди обмена + ) + is + REXSQUEUE EXSQUEUE%rowtype; -- Запись позиции очереди + RFISCDOC UDO_V_FISCDOCS%rowtype; -- Запись фискального документа + CTMP clob; -- Буфер для хранения данных ответа сервера + begin + /* Считаем запись очереди */ + REXSQUEUE := GET_EXSQUEUE_ID(NFLAG_SMART => 0, NRN => NEXSQUEUE); + /* Проверим что позиция очереди корректна */ + 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; /* Разбираем ответ */ CTMP := BLOB2CLOB(LBDATA => REXSQUEUE.RESP, SCHARSET => 'UTF8'); if (CTMP is null) then @@ -60,20 +249,153 @@ create or replace package body UDO_PKG_EXS_ATOL as /* Отработка ответов АТОЛ (v4) на запрос сведений о зарегистрированном документе (ФФД 1.05) */ procedure V4_FFD105_PROCESS_GET_BILL_INF ( - NIDENT in number, -- Идентификатор процесса - NEXSQUEUE in number -- Регистрационный номер обрабатываемой позиции очереди обмена + NIDENT in number, -- Идентификатор процесса + NEXSQUEUE in number -- Регистрационный номер обрабатываемой позиции очереди обмена ) is - REXSQUEUE EXSQUEUE%rowtype; -- Запись позиции очереди - CTMP clob; -- Буфер для хранения данных ответа сервера + REXSQUEUE EXSQUEUE%rowtype; -- Запись позиции очереди + 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; / diff --git a/db/UDO_P_FISCDOCS_BASE_SEND.prc b/db/UDO_P_FISCDOCS_BASE_SEND.prc new file mode 100644 index 0000000..76bc894 --- /dev/null +++ b/db/UDO_P_FISCDOCS_BASE_SEND.prc @@ -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; +/ diff --git a/db/UDO_P_FISCDOCS_GET_STATE_ATOL.prc b/db/UDO_P_FISCDOCS_GET_STATE_ATOL.prc new file mode 100644 index 0000000..9143a9d --- /dev/null +++ b/db/UDO_P_FISCDOCS_GET_STATE_ATOL.prc @@ -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; +/ diff --git a/db/UDO_P_FISCDOCS_MAKE_MSG_ATOL.prc b/db/UDO_P_FISCDOCS_MAKE_MSG_ATOL.prc index 12c53b2..45ffcff 100644 --- a/db/UDO_P_FISCDOCS_MAKE_MSG_ATOL.prc +++ b/db/UDO_P_FISCDOCS_MAKE_MSG_ATOL.prc @@ -1,20 +1,18 @@ create or replace procedure UDO_P_FISCDOCS_MAKE_MSG_ATOL ( - NCOMPANY in number, -- Организация - NFISCDOC in number, -- Регистрационный номер фискального документа - NEXSQUEUE out number -- Регистрационный номер добавленной позиции очереди обмена + NCOMPANY in number, -- Организация + NFISCDOC in number, -- Регистрационный номер фискального документа + NEXSQUEUE out number -- Регистрационный номер добавленной позиции очереди обмена ) as /* Локальные переменные */ - CDATA clob; -- Буфер для XML-посылки - SEXSSERVICE EXSSERVICEFN.CODE%type; -- Код сервиса-обработчика - SEXSSERVICEFN EXSSERVICEFN.CODE%type; -- Код функции отправки - NTMP_RN PKG_STD.TREF; -- Буфер для вычислений + CDATA clob; -- Буфер для XML-посылки + NTMP_RN PKG_STD.TREF; -- Буфер для вычислений /* Добавление пустой открытой ветки */ procedure NODE ( - SNAME varchar2 -- Имя ветки + SNAME varchar2 -- Имя ветки ) as begin @@ -27,8 +25,8 @@ as /* Добавление ветки со значением (строка) */ procedure NODE ( - SNAME varchar2, -- Имя ветки - SVALUE varchar2 -- Значение ветки + SNAME varchar2, -- Имя ветки + SVALUE varchar2 -- Значение ветки (строка) ) as begin @@ -45,8 +43,8 @@ as /* Добавление ветки со значением (число) */ procedure NODE ( - SNAME varchar2, -- Имя ветки - NVALUE number -- Значение ветки + SNAME varchar2, -- Имя ветки + NVALUE number -- Значение ветки (число) ) as begin @@ -63,8 +61,8 @@ as /* Добавление ветки со значением (дата) */ procedure NODE ( - SNAME varchar2, -- Имя ветки - DVALUE date -- Значение ветки + SNAME varchar2, -- Имя ветки + DVALUE date -- Значение ветки (дата) ) as begin @@ -92,23 +90,7 @@ begin from UDO_V_FISCDOCS T 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; + loop /* Данные заголовка фискального документа */ 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, diff --git a/modules/parus_atol_v4_ffd1.05.js b/modules/parus_atol_v4_ffd1.05.js index 3dbac43..003543f 100644 --- a/modules/parus_atol_v4_ffd1.05.js +++ b/modules/parus_atol_v4_ffd1.05.js @@ -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)) + }; }; //-----------------