Использование временной таблицы как источника данных сабформы NAV

Немного теории

У переменной типа Record есть логическое свойство Temporary (Временный). При установке этого свойства в TRUE, вашему приложению будет доступна структура, триггеры и функции исходной таблицы, а вот реальные данные из БД – нет. То есть, таблица изначально пустая, а все операции над экземпляром этой таблицы будут утеряны, как только работа приложения завершится. Никакого влияния на реальные данные в этой таблицы временная таблица не имеет. Все операции проходят в оперативной памяти машины во время исполнения приложения.

Это классная вещь, которая позволяет играться с таблицами без боязни накосорезить в данных БД. Объявление этого свойства выглядит следующим образом:

Свойство формы «SourceTable»

Цветочки начинаются в момент, когда мы хотим показать данные из временной таблицы на форме, а не только использовать их в коде. Да, в пятой версии NAV есть мощное свойство, которое полностью решает наши проблемы (все переходим на пятерку :). Однако, не все клиенты перейдут на новую версию даже в ближайшем будущем (кстати, новое свойство тоже надо использовать без фанатизма, иначе в командной работе, а особенно при использовании команды DELETEALL можно получить очень некрасивый результат). Поэтому давайте зайдем с другой стороны, и по шагам разберем метод, который позволяет делать то же самое и на 4-й, и даже на 3-й версии.

Свойство формы SourceTable не допускает перегрузки для использования временных таблиц (прим.переводчика – перегрузка – это термин из ООП, а не из транспортного цеха).  Просто выбрать таблицу, и все. Нам необходимо перенаправить стандартные функции формы, чтобы исключить обработку записей таблицы по умолчанию. Покажу это на простом примере:

Пример

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

А во время очередного совещания наш любимый клиент произносит такую фразу – «И неплохо было бы видеть все контакты клиента в списке» Неплохо… На самом деле, это означает что это ДОЛЖНО быть сделано.

Решение

Отлично, перед нами типичная задача типа Мастер Данные/детализация. Отличие от типичной в том, что детализация раскидана по нескольким таблицам, но должна выглядеть так, как будто все хранится в одной-единственной. Как это сделать? С помощью временной таблицы.

Форма детализации (сабформа)

Начнем с формы детализации (наша будущая сабформа). Сначала выберем таблицу, структура которой оптимально подходила был под все наши задачи (и под все нужные таблицы). В нашем пример это будет Sales Header (просто потому, что заголовочные таблицы для учтенных и архивных документов имеют практически одинаковую структуру, а уж контакты мы как-нибудь впихнем).


Как видно на скриншоте, важно правильно указать некоторые свойства формы. Пользователя нельзя допускать до редактирования таблиц, т.ч. мы максимально режем все права – для стабильности конечного результата. Т.к. мы не будем использовать поле Document Type по прямому назначению, то немного поиграемся с его свойствами на форме. В частности, переопределим под наши нужды OptionString, OptionCaption’ы и немного кода

Дальше – серьезнее. Мы определяем временный экземпляр таблицы 36 Sales Header (rRecTemp) и связываем несколько триггеров на форме с этой переменой вместо Rec (переменной формы по умолчанию). Нам необходимо перехватывать OnFindRecord, OnNextRecord и OnModifyRecord, чтобы изменить поведение формы по умолчанию.

Глобальные переменные сабформы:
Name DataType SubType

rTempRec               Record  Sales Header   (свойство Temporary=Yes)

rCont          Record  Contact

rSH            Record  Sales Header

rSIH           Record  Sales Invoice Header

rSSH           Record  Sales Shipment Header

rSHA           Record  Sales Header Archive

Триггеры на сабформе:

Form - OnFindRecord(Which : Text[1024]) : Boolean

rRecTemp.COPY(Rec);
IF rRecTemp.FIND(Which) THEN BEGIN
Rec := rRecTemp;
EXIT(TRUE);
END ELSE
EXIT(FALSE);
Form - OnNextRecord(Steps : Integer) : Integer

rRecTemp.COPY(Rec);
locResultSteps := rRecTemp.NEXT(Steps);
IF locResultSteps <> 0 THEN
Rec := rRecTemp;
EXIT(locResultSteps);
Form - OnModifyRecord() : Boolean
ModifyRec;
EXIT(FALSE);

Переходим к изюминке. Мы будем использовать свою функцию OnModify, а триггер OnModifyRecord отрабывать будет с результатом FALSE. Это нарушит обработку OnModifyRecord по умолчанию.

Наша функция OnModify перекрывает таковую из переменной Rec – обновлением нашей временной таблицы rRecTemp:

ModifyRec()

rRecTemp := Rec;
rRecTemp.MODIFY;

Теперь необходимо подготовить небольшую функцию SetTempRecord для обновления rRecTemp на сабформе каждый раз, когда пользователь переходит к следующему клиенту на главной форме (мастер-форме). Эта функция будет вызываться с передачей rRecTemp по ссылке, а содержимое переменной будет обновляться в триггере OnAfterGetCurrentRecord на мастер-форме.

Сначала мы очищаем табличку rRecTemp, а потом заполняем ее результатами работы триггера в мастер-форме. В итоге – видим в сабформе новые данные.

SetTempRecord(VAR rRecIn : TEMPORARY Record "Sales Header")

rRecTemp.DELETEALL;
IF rRecIn.FINDSET(FALSE) THEN
REPEAT
rRecTemp.COPY(rRecIn);
IF rRecTemp.INSERT THEN;
UNTIL rRecIn.NEXT = 0;
CurrForm.UPDATE;

Мастер-форма

Задачи, которые связаны с мастер-формой, достаточно просты. Однако, пошевелить мозгом все же придется. Нам необходимо обновлять содержимое временной таблицы T36 каждый раз когда пользователь перещелкивает клиента, и передавать их в сабформу, используя функцию SetTempRecord.
Глобальные переменные на мастер-форме:

Name           DataType       SubType

rTempRec               Record  Sales Header   (property Temporary=Yes)
rCont          Record  Contact
rContBusRel    Record  Contact Business Relation
rSH            Record  Sales Header
rSIH           Record  Sales Invoice Header
rSSH           Record  Sales Shipment Header
rSHA           Record  Sales Header Archive

Form - OnAfterGetCurrRecord()

// Не забудьте тысячу раз проверить, что переменная rRecTemp создана ВРЕМЕННОЙ – до тестирования готового кода :-)

rRecTemp.DELETEALL;
CLEARALL;

// Заказы и квоты
rSH.SETCURRENTKEY("Sell-to Customer No.");
rSH.SETRANGE("Sell-to Customer No.","No.");
rSH.SETRANGE("Document Type",rSH."Document Type"::Quote,rSH."Document Type"::Order);
IF rSH.FINDSET(FALSE) THEN
REPEAT
rRecTemp.INIT;
rRecTemp."Document Type" := rSH."Document Type";
rRecTemp."No." := rSH."No.";
rRecTemp."Sell-to Customer No." := "No.";
rRecTemp."Posting Date" := rSH."Posting Date";
rRecTemp."Document Date" := rSH."Document Date";
rRecTemp."Posting Description" := rSH."Posting Description";
rRecTemp."No. Printed" := 36; // покажем на форме номер исходной таблицы
IF NOT rRecTemp.INSERT(FALSE) THEN;
UNTIL rSH.NEXT=0;

// Учтенные счета – используем option Invoice
rSIH.SETCURRENTKEY("Sell-to Customer No.");
rSIH.SETRANGE("Sell-to Customer No.","No.");
IF rSIH.FINDSET(FALSE) THEN
REPEAT
rRecTemp.INIT;
rRecTemp."Document Type" := rRecTemp."Document Type"::Invoice;
rRecTemp."No." := rSIH."No.";
rRecTemp."Sell-to Customer No." := "No.";
rRecTemp."Posting Date" := rSIH."Posting Date";
rRecTemp."Document Date" := rSIH."Document Date";
rRecTemp."Posting Description" := rSIH."Posting Description";
rRecTemp."No. Printed" := 112;
IF NOT rRecTemp.INSERT(FALSE) THEN;
UNTIL rSIH.NEXT=0;

// Учтенные отгрузки – используем option Credit Memo
rSSH.SETCURRENTKEY("Sell-to Customer No.");
rSSH.SETRANGE("Sell-to Customer No.","No.");
IF rSSH.FINDSET(FALSE) THEN
REPEAT
rRecTemp.INIT;
rRecTemp."Document Type" := rRecTemp."Document Type"::"Credit Memo";
rRecTemp."No." := rSSH."No.";
rRecTemp."Sell-to Customer No." := "No.";
rRecTemp."Posting Date" := rSSH."Posting Date";
rRecTemp."Document Date" := rSSH."Document Date";
rRecTemp."Posting Description" := rSSH."Posting Description";
rRecTemp."No. Printed" := 110; // we'd like to show each source table on form
IF NOT rRecTemp.INSERT(FALSE) THEN;
UNTIL rSSH.NEXT=0;

// Архивные докменты продажи
// Тут возможен косяк с первичным ключом – т.к. в архиве могут храниться
// версий – нам это не важно – поэтому будем брать только первую версию :o)
rSHA.SETCURRENTKEY("Document Type","Sell-to Customer No.","No.","Doc. No. Occurrence","Version No.");
rSHA.SETRANGE("Sell-to Customer No.","No.");
IF rSHA.FINDSET(FALSE) THEN
REPEAT
rRecTemp.INIT;
rRecTemp."Document Type" := rSHA."Document Type" + 4;
rRecTemp."No." := rSHA."No.";
rRecTemp."Sell-to Customer No." := "No.";
rRecTemp."Posting Date" := rSHA."Posting Date";
rRecTemp."Document Date" := rSHA."Date Archived";
rRecTemp."Posting Description" := COPYSTR(rSHA."Posting Description" + ' (v.'+FORMAT(rSHA."Version No.")+')',1,
MAXSTRLEN(rSH."Posting Description"));
rRecTemp."No. Printed" := 5107;
IF NOT rRecTemp.INSERT(FALSE) THEN;
UNTIL rSHA.NEXT=0;

// Контакты
rContBusRel.SETCURRENTKEY("Link to Table","No.");
rContBusRel.SETRANGE("Link to Table",rContBusRel."Link to Table"::Customer);
rContBusRel.SETRANGE("No.","No.");
IF rContBusRel.FINDFIRST THEN BEGIN
rCont.SETCURRENTKEY("Company No.");
rCont.SETRANGE("Company No.",rContBusRel."Contact No.");
IF rCont.FINDSET(FALSE) THEN
REPEAT
rRecTemp.INIT;
rRecTemp."Document Type" := 6; // we just use something unique
rRecTemp."No." := rCont."No.";
rRecTemp."Sell-to Customer No." := "No.";
rRecTemp."Posting Description" := COPYSTR(rCont.Name,1, MAXSTRLEN(rSH."Posting Description"));
rRecTemp."Your Reference" := rCont."Phone No.";
rRecTemp."No. Printed" := 5050; // we'd like to show each source table on form
IF NOT rRecTemp.INSERT(FALSE) THEN;
UNTIL rCont.NEXT=0;
END;

// Вызываем нашу фунцию заполнения данных
CurrForm.frmDetails.FORM.SetTempRecord(rRecTemp);

Если вы уже заметили, мы пропустили несколько важных полей, которые обычно должны быть заполнены (например, rRecTemp.»No. Printed»). В это и прелесь – необязательно заполнять все, мы не работаем с реальными данными. Можно позволить себе быть прагматичными – нам нужно хранить номер таблицы? Давайте возьмем любое integer-поле из Sales Header, которое еще не использовали — и будем использовать его. Вот в чем прелесть временных таблиц Navision :)

Итог — демонстрация

Объекты Navision

В приложенном архиве – фоб, а в нем две формы — (MasterForm and DetailsForm – те, которые на скриншотах). FOB-файл скомпилирован в Navision 4.00as shown above).

  1. F99911: Header form (Master)
  2. F99912: Details form (SubForm)

Если вы внимательно изучите код, то найдете кое-какую функциональность, которая не описана в статье. Наслаждайтесь :)

Оригинал статьи находится здесь — «How To use temporary table data on a subform?«

Автор:

В области Navision - с 2003 года. Профессиональные интересы: NAV, MS SQL, .NET, BPMN, IT-менеджмент. Предметная область: логистика, финансы, склады, 3PL.

Количество статей, опубликованных автором: 86.

Добавить комментарий