INSTANCE (ЭКЗЕМПЛЯР)

Давайте на время отойдем от классов метасхемы АБС Бисквит и рассмотрим бизнес классы. Так например, рассмотрим, что такое платежный документ:

opclass

Как мы понимаем, платежный документ в АБС Бисквите состоит из набора классов метасхемы таких как: op (Документ), op-entry (Проводки), kau-entry (Субаналитические проводки) и op-bank (банк корреспондент).

Instance формирует объект платежный документ и представляет собой связанную совокупность динамических временных таблиц, каждая из которых определяет объекты соответствующих классов метасхемы АБС Бисквит, составляющих данный объект. Поля таких таблиц определяются набором реквизитов, как основных, так и дополнительных, соответствующего класса. При этом в качестве наименования поля берется не код реквизита из метасхемы, а его транслитерация. Связано это с тем, что наименование поля таблицы не может быть кириллическим, а код реквизита - может.

Просмотреть транслитерируемое значение кода реквизита можно на самом реквизите в метасхеме, оно отображается в правом верхнем углу формы просмотра/редактирования реквизита.

Instance1.jpg

Транслитерируемое значение кода реквизита не хранится в базе данных, а определяется функцией GetMangledName из библиотеки для работы со строками и списками.  

Для того чтобы нам, например, сформировать печатную форму интересующего нас документа, нам нужно сделать несколько запросов: получить все его проводки (таблица op-entry), получить реквизиты банка получателя/плательщика (op-bank), да и еще пробежаться по дополнительным реквизитам (таблицы signs и tmpsigns).

Instance позволяет получить весь объект (сразу оговорюсь еще раз, не как объект отдельного класса метасхемы АБС Бисквит, а как объект - платежный документ) в виде набора динамических временных таблиц, содержащих всю желаемую информацию по данному платежному документу.

Как же определяется, какие таблицы должны описывать наш объект, т.е. какие классы метасхемы АБС Бисквит задействованы в описании платежного документа? Структура нашего платежного документа определяется набором агрегируемых классов класса op, а так же их агрегируемых классов и т.д.

То, какие объекты агрегируемых классов попадают в соответствующие динамические таблицы Instance, определяется условиями агрегации заданными на реквизитах агрегации в метасхеме.

Что позволяет Instance:

  • Получить полную информацию по объекту;
  • Копировать объекты;
  • Редактировать объекты;
  • Создавать объекты;
  • Удалять объекты.

Помимо реквизитов класса, таблицы Instance содержат ряд технических полей:

Поле Тип данных Описание
__class character Класс метасхемы
__table character Таблица БД
__template logical Признак того, что запись содержит шаблон объекта
__mode int64 Режим изменения записи
__rowid rowid Ссылка на запись в БД
__uptable handle Ссылка на агрегирующую таблицу
__id int64 Идентификатор записи в наборе данных, нумерация начинается с 0
__upid int64 Ссылка на идентификатор в родительской таблице, нумерация начинается с 0
__tmpl-id int64 Ссылка на идентификатор шаблона транзакции
__manual int64 В случае не нулевого значения запись не обрабатывается транзакцией при инициализации поля __template
__private-data character Произвольные пользовательские данные
__continue logical Служебное поле для пропуска ошибок обработки (продолжать обработку шаблона даже при ошибке) Применяется в методе поиска. Может применяться также в других методах, которые умеют использовать !-методы
__prnt-cls-lst character Служебное поле для хранения списка родителей, для исключения зацикливания. Если добавляемый класс имеется в списке родителей, то он пропускается   

Индексы:

Индекс Поля индекса Описание
 __i__id  __id
 __i__upid  __upid
 __i__template  __template
 __i__tmpl-id  __tmpl-id

Описание атрибутов виджетов набора данных:

  • TEMP-TABLE:ADM-DATA - содержит параметры создания набора данных, разделенных CHR(1):  {&C_INST}~001<список агрегатов>~001<список пустых агрегатов>~001<список доп.реквизитов>~001<undo>;
  • TEMP-TABLE:PRIVATE-DATA - содержит handle поля агрегирующей таблицы, через которое осуществляется агрегация;
  • BUFFER:ADM-DATA - список из двух элементов разделенных CHR(1), в первом списке хранится список полей, являющихся агрегатами, во втором список полей, являющихся доп. реквизитами. ссылка на агрегируемую таблицу;
  • BUFFER:PRIVATE-DATA - содержит handle буфера, связанного с  таблицей БД для поддержки блокировок;
  • FIELD:PRIVATE-DATA:
    • для полей типа handle - ссылка на агрегируемую временную таблицу;
    • для поля __id - последовательность для уникальной идентификации записей во временной таблице; 
    • для поля __template - значение YES указывает на то, что набор данных был закэширован и удален и может быть использован повторно;
    • для полей доп. реквизитов - содержит константу FORM_DEF, означающую обязательность сохранения этого реквизита в БД.
  • FIELD:VALIDATE-MESSAGE - содержит выражение WHERE, описывающее связь между агрегируемыми классами;
  • FIELD:ADM-DATA - имеет значение YES, если наборы данных связаны отношением  ИСПОЛЬЗОВАНИЕ;
  • FIELD:LABEL и FIELD:COLUMN-LABEL - зарезервированы для пользователей

Работа с Instanсe осуществляется с помощью процедур и функций библиотеки для работы с отвязанным набором данных

Перед формированием Instance возможно определить перечень реквизитов агрегации для которых будут сформированы соответствующие их классам динамические таблицы. Другими словами, возможно формирование не всех таблиц Instance, а только определенных. Задание необходимого состава Instance осуществляется с помощью процедуры PrepareInstance вызываемой перед его формированием.

К настройке Instance следует подходить очень разумно. Желательно указывать только те реквизиты агрегации, которые вы действительно планируете использовать. Посмотрим на класс loan (договора), у него достаточно большое количество агрегируемых реквизитов, у классов которых так же имеются свои реквизиты агрегации и т.д. Следовательно будет сформировано огромное количество таблиц, а это очень ресурсоемко и производительность будет не на высоте. При этом большинством из них вы даже не воспользуетесь. Стоит ли так впустую расходовать ресурсы системы? - думаю, что нет.

Просмотреть сформированный Instance можно с помощью процедуры instview.p

{globals.i}
{tmprecid.def}
{intrface.get data}

DEF VAR vClass    AS CHAR        NO-UNDO.
DEF VAR vSurr     AS CHAR        NO-UNDO.
DEF VAR mInstance AS HANDLE      NO-UNDO.
DEF VAR mOk       AS LOGICAL     NO-UNDO.

FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-LOCK NO-ERROR.

IF AVAIL op THEN
DO:
   /* Определим класс и суррогат документа */
   ASSIGN
      vClass = op.class-code
      vSurr  = STRING(op.op)
   .
   /* Определим структуру Instance */
   RUN PrepareInstance ("*").
   /* Сформируем Instance отобранного документа */
   RUN GetInstance IN h_data (vClass,
                              vSurr,
                              OUTPUT mInstance,
                              OUTPUT mOk).

   /* Вызов вьювера набора данных */

   IF mOk THEN
      RUN instview.p(mInstance).
   ELSE
   DO:
      {message "ОШИБКА Instance !"}.
   END.
END.
/* Удаляем Instance */
IF VALID-HANDLE(mInstance)
   THEN RUN DelEmptyInstance IN h_data (mInstance).

{intrface.del}    


ПРИМЕРЫ



Получение значения реквизита основного класса Instance.

Получим значение реквизита name-send класса op.

{globals.i}
{tmprecid.def}
{intrface.get data}

DEF VAR vClass     AS CHAR        NO-UNDO.
DEF VAR vSurr      AS CHAR        NO-UNDO.

DEF VAR mInstance  AS HANDLE      NO-UNDO.
DEF VAR mTmpBuffer AS HANDLE      NO-UNDO.
DEF VAR mOk        AS LOGICAL     NO-UNDO.
DEF VAR vValue     AS CHAR        NO-UNDO.

FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-LOCK NO-ERROR.

IF AVAIL op THEN
DO:
   /* Определим класс и суррогат документа */

   ASSIGN
      vClass = op.class-code
      vSurr  = STRING(op.op)
   .

  /* Определим структуру Instance */
   RUN PrepareInstance ("*").

   /* Сформируем Instance отобранного документа */
   RUN GetInstance IN h_data(vClass,
                   vSurr,
                   OUTPUT mInstance,
                   OUTPUT mOk).

   /* Получим необходимое нам значение */
   IF mOk THEN
   DO:
      mTmpBuffer = mInstance:DEFAULT-BUFFER-HANDLE
      mTmpBuffer:FIND-FIRST().
      vValue = mTmpBuffer:BUFFER-FIELD("name-send"):BUFFER-VALUE NO-ERROR.
   END.
   ELSE
   DO:
      {message "ОШИБКА Instance !" }.
   END.
END.

{setdest.i}
   PUT UNFORMATTED  vValue FORMAT "x(50)" SKIP.
{preview.i}


/* Удаляем Instance */

IF VALID-HANDLE(mInstance)
   THEN RUN DelEmptyInstance IN h_data (mInstance).

{intrface.del}                                                                                  


Получение значение реквизита агрегируемого класса Instance. 

Получим значение счета кредита 2-ой проводки многопроводочного документа (реквизит acct-cr). На классе op заведено 4 реквизита для агрегируемого класса op-entry: op-entry1, op-entry2, op-entry3 и op-entry.

Для реквизита op-entry с условием  агрегации of op при формировании Instance формируется динамическая таблица содержащая все проводки документа. Для реквизитов op-entry1, op-entry2 и op-entry3 формируются таблицы содержащие по одной записи соответствующей проводки документа.

Таким образом, у нас есть два варианта получения счета кредита: из таблицы для реквизита op-entry2 и из таблицы для реквизита op-entry, но в этом случае нам необходимо будет задать условие поиска записи с op-entry = 2. 

{globals.i}
{tmprecid.def}
{intrface.get data}

DEF VAR vClass     AS CHAR        NO-UNDO.
DEF VAR vSurr      AS CHAR        NO-UNDO.

DEF VAR mInstance  AS HANDLE      NO-UNDO.
DEF VAR mH         AS HANDLE      NO-UNDO.
DEF VAR mTmpBuffer AS HANDLE      NO-UNDO.
DEF VAR mOk        AS LOGICAL     NO-UNDO.
DEF VAR vValue     AS CHAR        NO-UNDO.

FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-LOCK NO-ERROR.

IF AVAIL op THEN
DO:
   /* Определим класс и суррогат документа */
   ASSIGN
      vClass = op.class-code
      vSurr  = STRING(op.op)
   .

   /* Определим структуру Instance */
   RUN PrepareInstance ("op-entry2").

   /* Сформируем Instance отобранного документа */
   RUN GetInstance IN h_data (vClass,
                   vSurr,
                   OUTPUT mInstance,
                   OUTPUT mOk).

   /* Получим необходмиое нам значение */
   IF mOk THEN
   DO:
      mTmpBuffer = mInstance:DEFAULT-BUFFER-HANDLE.
      mTmpBuffer:FIND-FIRST().

/* определяем handle для таблицы реквизита op-entry */
      mH = mTmpBuffer:BUFFER-FIELD("op-entry"):BUFFER-VALUE NO-ERROR.
     
/* работаем с  таблицей агрегируемого класса op-entry */
      mTmpBuffer = mH:DEFAULT-BUFFER-HANDLE.
      mTmpBuffer:FIND-FIRST("WHERE op-entry = 2").
      vValue = mTmpBuffer:BUFFER-FIELD("acct-cr"):BUFFER-VALUE NO-ERROR.
   END.
   ELSE
   DO:
      {message "ОШИБКА Instance !" }.
   END.
END.

{setdest.i}
   PUT UNFORMATTED  vValue FORMAT "x(50)" SKIP.
{preview.i}

/* Удаляем Instance */
IF VALID-HANDLE(mInstance)
   THEN RUN DelEmptyInstance IN h_data (mInstance).

{intrface.del}                                                                                  


Удаление объекта.

Удаление объекта из базы данных посредством Instance осуществляется с помощью процедуры DelInstance, в качестве параметра которой необходимо передать handle Instace удаляемого объекта. Таким образом первым делом необходимо сформировать Instance этого объекта. Далее процедура последовательно удаляет все объекты в базе данных входящих в состав сформированного Instance начиная с главного класса.

Для примера, рассмотрим удаление платежного документа. Тут нас ждет одна проблема. Главным классом для Instance будет являться класс op и следовательно запись именно этого класса будет удаляться первой. На таблице op определен триггер на событие DELETE, который не позволяет удалить запись op первой, так как у документа имеются балансовые проводки (op-entry) и требует сначала удалить их, а потом уже и сам документ (op). Но процедура DelInstance делает все наоборот. Поэтому, для удаления документа нам придется отключить триггер.

Отключить триггер можно воспользовавшись инклюд-файлом dis-trig.i, которому в качестве аргументов необходимо передать таблицу и событие для которого необходимо отключить триггер.

Далее проблема заключается в том, что триггер не только проверяет наличие балансовых проводок по удаляемому документу, а делает еще и другие проверки, которые мы тем самым проигнорируем.

{globals.i}
{tmprecid.def}
{dis-trig.i
  "op"    "delete"
}
{intrface.get data}

DEF VAR vClass    AS CHAR        NO-UNDO.
DEF VAR vSurr     AS CHAR        NO-UNDO.
DEF VAR mInstance AS HANDLE      NO-UNDO.
DEF VAR mOk       AS LOGICAL     NO-UNDO.

FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-LOCK NO-ERROR.
IF AVAIL op THEN
DO:
   /* Определим класс и суррогат */
   ASSIGN
      vClass = op.class-code
      vSurr  = STRING(op.op).
   .
   /* Сформируем Instance отобранного документа */
   RUN PrepareInstance ("*").
   RUN GetInstance IN h_data (vClass,
                              vSurr,
                              OUTPUT mInstance,
                              OUTPUT mOk).
   /* Удаляем документ */
   IF mOk THEN
      RUN DelInstance IN h_data (vClass,
                                 mInstance,
                                 YES,
                                 OUTPUT mOk).
   IF mOk THEN
   DO:
      {message "Объект удален!"}
   END.
   ELSE
   DO:
      {message "ОШИБКА удаления объекта!"}
   END.
END.

{intrface.del}

Удаление экземпляра объекта так же возможно с помощью процедуры SetInstance. Для этого, предварительно, режим изменения необходимо установить в значение {&MOD_DELETE}.

{globals.i}
{tmprecid.def}
{form.def}

{dis-trig.i
  "op"    "delete"
}

{intrface.get data}

DEF VAR vClass    AS CHAR        NO-UNDO.
DEF VAR vSurr     AS CHAR        NO-UNDO.
DEF VAR mInstance AS HANDLE      NO-UNDO.
DEF VAR mOk       AS LOGICAL     NO-UNDO.

FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-ERROR.

IF AVAIL OP THEN
DO:
   /* Определим класс и суррогат */
   ASSIGN
      vClass = op.class-code
      vSurr  = STRING(op.op).
   .
  /* Сформируем Instance отобранного документа */
   RUN PrepareInstance ("*").

   RUN GetInstance IN h_data (vClass,
                              vSurr,
                              OUTPUT mInstance,
                              OUTPUT mOk).
   /* Установим режим - удаление */
   RUN SetInstanceProp(mInstance,"__mode","{&MOD_DELETE}",OUTPUT mOk).
   /* Удаляем объект */
   IF mOk THEN
      RUN SetInstance IN h_data (vClass,
                                 mInstance,
                                 OUTPUT mOk).
   IF mOk THEN
   DO:
      {message "Объект удален!"}.
   END.
   ELSE
   DO:
      {message "ОШИБКА удаления объекта!"}.
   END.
END.
{intrface.del}

Экспорт Instance в XML

Для экспорта Instance в XML предварительно соберем все таблицы составляющие его в DATASET, после чего осуществим сам экспорт с помощью метода WRITE-XML. При этом, перед формированием самого Instance необходимо определить глобальную переменную gInstanceXml = YES, которая указывает на необходимость замены спец. символов при транслитерации реквизитов в функции GetMangledName.

{globals.i}
{tmprecid.def}
{intrface.get data}

DEF NEW GLOBAL SHARED VAR gInstanceXml  AS LOG  NO-UNDO.

DEF VAR vClass      AS CHAR        NO-UNDO.
DEF VAR vSurr       AS CHAR        NO-UNDO.

DEF VAR hInstance   AS HANDLE      NO-UNDO.
DEF VAR hAggr       AS HANDLE      NO-UNDO.
DEF VAR hField      AS HANDLE      NO-UNDO.
DEF VAR hBuff       AS HANDLE      NO-UNDO.
DEF VAR hDSet       AS HANDLE      NO-UNDO.

DEF VAR mOk         AS LOGICAL     NO-UNDO.
DEF VAR vAggrFields AS CHAR        NO-UNDO.
DEF VAR vNumFields  AS INT64       NO-UNDO.
DEF VAR vCnt        AS INT64       NO-UNDO.

gInstanceXml = YES.

FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-LOCK NO-ERROR.

IF AVAIL op THEN
DO:
   /* Определим класс и суррогат документа */
   ASSIGN
      vClass = op.class-code
      vSurr  = STRING(op.op)
   .
   /* Определим структуру Instance */
   RUN PrepareInstance ("op-entry,op-bank").
   /* Сформируем Instance отобранного документа */
   RUN GetInstance IN h_data (vClass,
                              vSurr,
                              OUTPUT hInstance,
                              OUTPUT mOk).
   IF mOk THEN
   DO:
      hBuff = hInstance:DEFAULT-BUFFER-HANDLE.
      /* Список агригируемых реквизитов */
      vAggrFields = ENTRY(1,hBuff:ADM-DATA,CHR(1)).
      vNumFields  = NUM-ENTRIES(vAggrFields).
      /* Создаем DATASET */
      CREATE DATASET hDSet.
      hDSet:ADD-BUFFER(hBuff).
      /* Проходим по всем агригируемым реквизитам */
      DO vCnt = 1 TO vNumFields:
         hField = hBuff:BUFFER-FIELD(ENTRY(vCnt,vAggrFields)).
         hAggr  = hField:BUFFER-VALUE.
         IF VALID-HANDLE(hAggr) THEN
         hDSet:ADD-BUFFER(hAggr:DEFAULT-BUFFER-HANDLE).
      END.
   END.
   hDSet:WRITE-XML("FILE","op.xml",FALSE,"windows-1251").
END.

/* Удаляем DATASET */
IF VALID-HANDLE(hDSet)
   THEN DELETE OBJECT hDSet.

/* Удаляем Instance */
IF VALID-HANDLE(hInstance)
   THEN RUN DelEmptyInstance IN h_data (hInstance).

gInstanceXml = NO.

{intrface.del del}

Данная процедура осуществляет выгрузку отмеченного в браузере документов платежного документа в файл op.xml.

В заключение хотелось бы сказать, что на использовании Instance построен механизм универсальных транзакций и метод Form класса метасхемы.

Вы здесь: Главная ИБС Бисквит МЕТАСХЕМА АБС БИСКВИТ INSTANCE - ЭКЗЕМПЛЯР ОБЪЕКТА