СТАНДАРТНЫЕ ТРАНЗАКЦИИ: ИМПОРТ ДОКУМЕНТОВ
Задача интеграции АБС с внешними системами уверен хотя бы раз стояла перед вами. В АБС Бисквит имеется отдельный модуль посвященный этому вопросу - ОБМЕН С ВНЕШНИМИ СИСТЕМАМИ. Имеется так же документированный механизм импорта объектов в теговом формате.
Внешних систем огромное разнообразие и возможность экспорта данных есть почти во всех, но и форматы экспорта у всех систем разные и далеко не все из них соответствуют привычному АБС БИСКВИТ теговому формату. Это может быть текстовый формат, XML, DBF и т.д.
О том как научить АБС Бисквит понимать необходимый нам формат экспорта-импорта платежных документов мы здесь и поговорим.
Рассмотрим создание процедуры импорта платежных документов из внешних систем.
Для универсальности решения данной задачи разобьем ее на отдельные этапы (блоки):
- обработка параметров транзакции (правило обмена);
- чтение данных из файла (любой формат файла импорта);
- заполнение временные таблицы w-op, w-op-entry и т.д.;
- обработка шаблона транзакции соответствующего загружаемому документу;
- валидация импортируемых документов;
- перенос данных из временных таблиц в базу АБС;
- печать ведомости загрузки.
Каждый из этих этапов может быть реализован в отельном инклюд-файле, тем самым мы получим возможность конструировать различные процедуры импорта документов.
И так начнем. Объявим необходимые нам переменные, временные таблицы и т.д.
{globals.i}
{imp-mci.def &nodef="/*"}
DEF VAR vCount AS INT NO-UNDO.
DEF VAR vErrCount AS INT NO-UNDO.
DEF VAR iErr AS INT NO-UNDO.
DEF VAR vError AS CHAR NO-UNDO.
DEF VAR vCodeClass AS CHAR NO-UNDO.
DEF VAR vErrCode AS CHAR NO-UNDO.
DEF TEMP-TABLE tt-error NO-UNDO
FIELD err-message AS CHAR
.
DEF TEMP-TABLE tt-op
FIELD send-name AS CHAR
FIELD send-acct AS CHAR
FIELD ben-name AS CHAR
FIELD ben-acct AS CHAR
FIELD amt-rub AS DECIMAL
FIELD details AS CHAR
.
DEF STREAM ImportStream.
ОБРАБОТКА ПАРАМЕТРОВ ТРАНЗАКЦИИ
Произведем анализ транзакции импорта и ее шаблонов. Из транзакции нам необходимо определить основные параметры импорта:
- каталог импорта (каталог в котором будет осуществляться поиск файлов для импорта);
- маска импортируемых файлов;
- режим импорта рейс/автоматическая;
- параметры документов (статус, счета т.д.)
flag-go = 0. /* Вид импортируемых документов */
{justasec}
{imp-mci.ch1 &NoStat = "YES"}
{i-imp.del}
imp-mci.ch1 осуществляет обработку правила обмена и создает файл filelist.imp, в рабочем каталоге пользователя запустившего транзакцию, со списком импортируемых файлов, находящихся в каталоге импорта и попадающих под указанную маску, формирует запрос ввода номера рейса.
Возможность перезаписи рейса, с удалением загруженных ранее документов в рамках этого рейса осуществляется инклюд-файлом {i-imp.del}.
ЧТЕНИЕ ДАННЫХ ИЗ ФАЙЛА ИМПОРТА
Для примера, мы будем импортировать внутренние документы, например, загрузка реестра по выплате зарплаты, из следующего CSV файла со следующей структурой данных:
<Плательщик>;<ДБ>;<Получатель>;<КР>;<Сумма>;<Назначение платежа>
Приступим...
{debug.i "Чтение файлов из каталога"}
/* Цикл по файлам из каталога импорта */
INPUT STREAM FileListin FROM "filelist.imp".
REPEAT:
/* Получение i-го файла в списке */
IMPORT STREAM FileListin file-name NO-ERROR.
/* Каждый файл обрабатываем в отдельной транзакции */
DO TRANSACTION ON ERROR UNDO, LEAVE:
INPUT STREAM ImportStream FROM VALUE(file-name).
/* Строка файла */
REPEAT:
CREATE tt-op.
/* Чтение i-ой записи из файла во временную таблицу tt-op */
IMPORT STREAM ImportStream DELIMITER ";" tt-op.
END.
INPUT STREAM ImportStream CLOSE.
END.
END.
INPUT STREAM FileListin CLOSE.
{debug.i "Чтение файлов из каталога:выполнено"}
ЗАПОЛНЕНИЕ ВРЕМЕННЫХ ТАБЛИЦЫ
Сформируем на основе импортированных записей таблицы w-op, w-op-entry.
{debug.i "Формирование w-op, w-op-entry"}
vCount = 0.
FOR EACH tt-op:
vCount = vCount + 1.
CREATE w-op.
CREATE w-op-entry.
ASSIGN
w-op.op = vCount
w-op.filial-id = shFilial
w-op.op-date = TODAY
w-op.doc-type = IF op-template.doc-type NE ""
THEN op-template.doc-type
ELSE "01"
w-op.doc-num = STRING(vCount)
w-op.user-id = mail-user.user-id
w-op.acct-cat = "b"
w-op.op-kind = op-kind.op-kind
w-op.op-status = status-done
w-op.inn = ""
w-op.doc-date = in-op-date
w-op.ins-date = in-op-date
w-op.due-date = in-op-date
w-op.contract-date = in-op-date
w-op.bank-code-rec = bank-mfo-9
w-op.bank-code-send = bank-mfo-9
w-op.details = tt-op.details
w-op-entry.op = vCount
w-op-entry.op-entry = 1
w-op-entry.op-date = w-op.op-date
w-op-entry.user-id = w-op.user-id
w-op-entry.acct-cat = w-op.acct-cat
w-op-entry.op-status = w-op.op-status
w-op-entry.acct-db = IF op-template.acct-db EQ ?
THEN tt-op.send-acct
ELSE op-templ.acct-db
w-op-entry.acct-cr = IF op-template.acct-cr EQ ?
THEN tt-op.ben-acct
ELSE op-templ.acct-cr
w-op-entry.amt-rub = tt-op.amt-rub
.
END.
{debug.i "Формирование w-op, w-op-entry: выполнено"}
Таким образом, мы занесли импортируемые документы во временные таблицы w-op, w-op-entry.
ВАЛИДАЦИЯ ИМПОРТИРУЕМЫХ ДОКУМЕНТОВ
Следующим этапом нам необходимо провалидировать документы. Валидацию документов во временных таблицах w-op, w-op-entry процедурой imp-upd.p мы уже рассматривали подробно в отдельной статье Проверка реквизитов документов.
{debug.i "Валидация импортируемых документов"}
RUN imp-upd.p (0, NO).
{debug.i "Валидация импортируемых документов: выполнено"}
Здесь мы рассматриваем импорт однотипных, в плане типа платежа, документов и валидацию импортируемых документов проводим одновременно всех после занесения их во временные таблицы. Если бы мы импортировали реестр содержащий не однотипные документы (внутренние, ответные обороты и т.д.), валидацию необходимо бы было проводить по каждому документу в отдельности, предварительно определив тип платежа, например, по БИКу банка получателя, передавая соответствующие значение входных параметров процедуре imp-upd для каждого документа.
После валидации в поле w-op.op-error записи документа прописываются коды ошибок классификатора ошибок, по которым документ не прошел валидацию в формате <классификатор>:<код ошибки>,...
Код ошибки в классификаторе может быть классифицирован как ошибка (критическая), или как предупреждение.
Документы имеющие "предупреждения" будем загружать со статусом "В - документ с ошибкой" для последующего анализа контролирующим сотрудником, а документы имеющие "ошибки" загружать вообще не будем и в последствии
vErrCount = 0.
FOR EACH w-op:
IF w-op.op-error NE "" THEN
DO:
FIND FIRST w-op-entry WHERE w-op-entry.op EQ w-op.op NO-LOCK NO-ERROR.
/*
ASSIGN
status-done = "В"
w-op.op-status = status-done.
w-op-entry.op-status = status-done.
*/
DO iErr = 1 TO NUM-ENTRIES(w-op.op-error):
vError = ENTRY(iErr, w-op.op-error).
ASSIGN
vCodeClass = ENTRY(1,vError,":")
vErrCode = ENTRY(2,vError,":")
.
Как вы можете заметить, присвоение статуса "В" документам у которых w-op.op-error <> "", т.е. имеющим какие-то ошибки, выявленных в процессе валидации, мы пока закомментировали. Для чего мы это сделали объясним чуть позже.
/*
Найдем ошибку в классификаторе и определим по нему ошибка это
или предупреждение. Если это ошибка, удалим запись документа,
предварительно занеся информацию о нем во временную таблицу
tt-error
*/
FIND FIRST code WHERE code.class EQ vCodeClass AND
code.code EQ vErrCode
NO-LOCK NO-ERROR.
/* Если это ошибка удаляем запись */
IF AVAIL code AND code.val EQ "Ошибка" THEN
DO:
CREATE tt-error.
ASSIGN
tt-error.err-message = w-op.op-error
.
DELETE w-op.
DELETE w-op-entry.
vErrCount = vErrCount + 1.
LEAVE.
END.
END.
END.
END.
ВЕДОМОСТИ ЗАГРУЗКИ
Вывод ошибок в ведомость может осуществляться процедурой DispErr описанной в imp-all.pro, которая в свою очередь объявленна в imp-mci.def.
{imp-err.def}
FOR EACH w-op NO-LOCK:
FIND FIRST w-op-entry WHERE w-op-entry.op EQ w-op.op NO-LOCK NO-ERROR.
RUN DispErr.
END.
{preview.i &stream="stream err" {&*}}
ПЕРЕНОС ДАННЫХ ИЗ ВРЕМЕННЫХ ТАБЛИЦ В БАЗУ АБС
Для переноса документов из временных таблиц в базу в АБС Бисквит имеется ряд готовых иклюд-файлов, таких как:
- imp-opcr.i
- imp-opcrkas.i
- imp-opcrmbk.i
- imp-opcr.mci
Рассмотрение их принципиальных отличий оставлю пока вам самим. Все они несильно отличаются друг от друга и основным из них является imp-opcr.i, вот на нем мы и остановимся.
Тут есть ряд особых моментов:
Если flag-go <> 121 (что за 121 пока не знаю), то какой бы статус не был указан в w-op, он будет заменен на единый для всех status-done, который мы определили вначале процедуры.
Если flag-go = 1 или 2, то будет проведена проверка на ошибки и уже загруженному в АБС документу будет изменен статус на "В", а при flag-go = 1 еще и счет кредита будет заменен на nacct-db. Но это все только если ошибка - это действительно ошибка, а не предупреждение, например.
Если же ошибка окажется предупреждением, или вообще ничем (по классификатору ошибок), то ничего изменено не будет, и документ, у которого есть предупреждения, будет загружен со статусом status-done
При flag-go = 0 или 3 таких преобразований не произойдет, и всем документам будет проставлен единый статус равный, значению переменной status-done.
Есть 2-а варианта, которые я вижу на текущий момент:
1) это изменять статусы уже загруженных в базу документам;
2) написать свой инклюд файл импорта документов на основе imp-opcr.i.
Здесь мы рассмотрим первый вариант, оставив второй на откуп вам.
Загрузку провалидированных документов в базу АБС будет осуществлять с помощью инклюд-файла imp-opcr.i.
{debug.i "Импорт документов"}
FIND FIRST w-op NO-LOCK NO-ERROR.
IF AVAIL w-op THEN
DO:
{imp-opcr.i
&NO-DEL-WOP="FALSE"}
END.
{debug.i "Импорт документов: выполнено"}
Теперь нам необходимо изменить статус загруженных документов.
{debug.i "Изменение статусов документов"}
FOR EACH w-op:
FIND FIRST op OF w-op NO-LOCK NO-ERROR.
IF AVAIL op THEN
DO:
{opent-st.i &status=w-op.op-status}
END.
END.
{debug.i "Изменение статусов документов:выполнено"}
На этом импорт документов в базу завершен.