Экономия своими руками
Когда проводят презентацию новой системы, связанной с IT областью, часто рассказывают о таком параметре как «стоимость владения», сопровождая обязательным словом «низкая». Обычно если об этом говорят, то это значит, что стоимость покупки сильно завышена, а производитель не сомневается в том, что его технология не потребует больших затрат на сопровождение. По большому счету это не самый плохой выбор.
Не знаю, как по этому параметру оценивают Navision. В этой статье я дам несколько советов, как снизить стоимость владения или просто сэкономить на покупке объектов в Navision применяя вполне легальные методы. Хочу обратить внимание на то, что речь идет о методах, которые позволяют решить проблему экономии средств на закупку дополнительных объектов Navision (таблицы, формы, отчеты) и не всегда эти методы будут самыми производительными по скорости выполнения или будут простыми по способу реализации.
Таблицы
Временные данные
Как правило, для хранения временных данных используются временные таблицы. Стандартное и вполне разумное решение, для любой СУБД. Но в Navision нет возможности создать временную таблицу произвольной структуры. Часто приходится сталкиваться с тем, что временная таблица не имеет всех необходимых полей для хранения данных. Кроме того вводит в заблуждение то, что в поле временной таблицы с именем, например «Код Товара», хранится значение не имеющее ни какого отношения к этому коду т.к. в исходной таблице нет поля с подходящим названием, типом и что самое плохое нужной длины. Так что, для хранения временных данных нужно покупать новую таблицу? Нет, не наш метод.
Попробуйте использовать XML. Причем советую использовать не стандартне для Navision XMLPort, а COM объект ‘Microsoft XML, vXXX’. У вас открывается возможность создать объект произвольной структуры, не привязанной к табличным ограничениям Navision. Если Вы планируете хранить несколько тысяч значений во временной таблице, то работает это также быстро, как и временная таблица. Ключевые значения советую хранить как узлы XML. Тогда можно быстро обращаться к нужной записи.
Заманчивыми является следующие возможности. В одном XML можно хранить несколько временных таблиц одновременно. Можно реализовать массив/список с произвольной глубиной, это то, что вообще нельзя сделать в Navision. Можно сохранять временные данные в файл, что в частности позволяет упростить отладку. Можно создать замену постоянно глючащему fin.zup и сохранять необходимые настройки в XML. В приложении к статье приведен пример как, используя XML создать интерфейс хранения в стиле ini файла.
Долгосрочное хранение
Как быть в случае, если нам нужно создать таблицу для длительного хранения информации? Давайте купим таблицу в Navision? Нет, не наш метод.
Попробуйте использовать ADO. Создайте таблицу в SQL сервере нужной структуры и заполняйте ее данными, используя стандартные команды INSERT, UPDATE и т.д. Советую так поступать для тех случаев, когда Вам необходимо хранить логи доступа или логи использования тех или иных объектов или их состояний. Как правило, в таких случаях Вам нужно только добавлять в таблицу логов данные, что очень просто сделать. Но ни в коем случае не думайте, что я Вас пытаюсь ограничить только этими задачами, дерзайте и ищите другие возможности по использованию ADO.
Пример:
Создаем таблицу в SQL
[TIMESTAMP] [TIMESTAMP] NOT NULL,
[ID] [INT] IDENTITY(1,1) NOT NULL,
[Date_Time] [datetime] NOT NULL,
[Message] [VARCHAR](250) NOT NULL,
[Message DATE] [datetime] NOT NULL,
[Message TIME] [datetime] NOT NULL,
CONSTRAINT [NAS Log$0] PRIMARY KEY CLUSTERED
(
[ID] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [DATA Filegroup 1]
) ON [DATA Filegroup 1]
В Navision создаем функцию для хранения логов
AddSQLLog(MessageText : Text[1024])
Name DataType Subtype Length
ADODB Automation 'Microsoft ActiveX Data Objects 2.8 Library'.Connection
StringConnect Text 1024
}
StringConnect:='driver={SQL Server};server='+Сервер+';uid='+IdUser+';pwd='+Password+';database='+ИмяБазы;
// инициализация библиотеки ADO
IF ISCLEAR(ADODB) THEN
IF NOT CREATE(ADODB) THEN ERROR('Не могу инициализировать библиотеку ADO !');
ADODB.CommandTimeout(3600);
ADODB.ConnectionString :=StringConnect;
ADODB.Open();
=ADODB.Execute(
'SET DATEFORMAT dmy ‘+
‘INSERT INTO [Log]'+
'([Date_Time],[Message],[Message Date],[Message Time])'+
'VALUES ('''+FORMAT(CREATEDATETIME(TODAY, TIME))+''','''+COPYSTR(MessageText,1,250)+''','''+
FORMAT(TODAY,0,'<day, 2>/<month, 2>/<year, 2>')+''',''1754-01-01 '+FORMAT(TIME)+''')'
);
ADODB.Close;
Еще ADO
Пример необычного использования ADO из реальной практики. Всем известно что, при учете документов в Navision возможен учет только одного документа в один и тот же момент времени. При большом количестве пользователей/документов учет превращается в кошмар белых экранов и длительных блокировок. Радикально решать задачу нужно на уровне учетных функции, но специалисты Navision/Micrososoft на протяжении всех версий даже не пытаются этого делать. Давайте хотя бы уберем белые экраны и покажем пользователю счетчик попыток учета. При этом в реальной очереди блокировки будет находиться один пользователь.
Проверьте наличие представления [Session] в Вашей БД. Если его нет, то создайте:
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE VIEW [dbo].[SESSION] AS
SELECT
CAST(SP.[spid] AS INTEGER) AS "Connection ID",
CAST(RTRIM(SP.[loginame]) AS NVARCHAR(64)) COLLATE Cyrillic_General_CI_AS AS "User ID",
CAST(CASE WHEN SP.[spid] = @@SPID THEN 1 ELSE 0 END AS TINYINT) AS "My Session",
CONVERT(DATETIME, '1754-01-01 '+CONVERT(CHAR(8), SP.[login_time], 108), 120) AS "Login Time",
CONVERT(DATETIME, CONVERT(CHAR(10), SP.[login_time], 120)+' 00:00:00:000', 121) AS "Login Date",
CAST (SD.[name] AS NVARCHAR(128)) COLLATE Cyrillic_General_CI_AS AS "Database Name",
CAST (RTRIM(SP.[program_name]) AS NVARCHAR(64)) COLLATE Cyrillic_General_CI_AS AS "Application Name",
CASE WHEN SP.[nt_domain] <> '' THEN 1 ELSE 0 END AS "Login Type",
CAST(RTRIM(SP.[hostname]) AS NVARCHAR(64)) COLLATE Cyrillic_General_CI_AS AS "Host Name",SP.[cpu] AS "CPU Time (ms)",
CASE WHEN SP.[memusage] < 0 THEN 0 ELSE SP.[memusage]*8 END AS "Memory Usage (KB)",
SP.[physical_io] AS "Physical I_O",
CAST(CASE WHEN SP.[blocked] <> 0 THEN 1 ELSE 0 END AS TINYINT) AS "Blocked",CAST(CASE WHEN SP.[blocked] <> 0 THEN SP.[waittime] ELSE 0 END AS INTEGER) AS "Wait Time (ms)",
CAST(SP.[blocked] AS INTEGER) AS "Blocking Connection ID",
CAST(ISNULL(RTRIM(SPB.[loginame]), '') AS NVARCHAR(64)) COLLATE Cyrillic_General_CI_AS AS "Blocking User ID",
CAST(ISNULL(RTRIM(SPB.[hostname]), '') AS NVARCHAR(64)) COLLATE Cyrillic_General_CI_AS AS "Blocking Host Name",
CAST('' AS NVARCHAR(64)) COLLATE Cyrillic_General_CI_AS AS "Blocking Object",
CASE WHEN SP.[cmd] = 'AWAITING COMMAND' THEN CAST(DATEDIFF(SECOND, SP.[last_batch], GETDATE()) AS BIGINT)*1000 ELSE 0 END AS "Idle Time"
FROM [master].[dbo].[sysprocesses] AS SP
JOIN [master].[dbo].[sysdatabases] AS SD
ON (SP.[dbid] = SD.[dbid])
LEFT OUTER JOIN [master].[dbo].[sysprocesses] AS SPB
ON (SP.[blocked] = SPB.[spid])
WHERE SP.[ecid] = 0 AND SP.[last_batch] >= CONVERT(DATETIME, '2000-01-01 00:00:00', 120)
GO
Предоставьте пользователю, через которого вы будете подключаться к SQL серверу право на просмотр этого представления.
Затем в Navision создайте функцию:
ПроверкаБлокировки(МаксПопыток : Integer) Result : Boolean
// Проверка блокировок пользователей
// МаксПопыток >0 - максимальное количество попыток
// =0 - без повторных попыток, но блокировки проверит
// <0 - пока не будет разблокирован
// Возврат
// True - последняя проверка нашла блокировки
// False - нет блокировки
///////////////////////////////////////////////////////////
//Либо создайте переменные либо измените в тексте программы
//Сервер
//IdUser
//ИмяБазы
//Password
////////////////////////////////////////////////////////////
{
Name DataType Subtype Length
ADODB Automation 'Microsoft ActiveX Data Objects 2.8 Library'.Connection
ADOREC Automation 'Microsoft ActiveX Data Objects Recordset 2.8 Library'.Recordset
ФирмаИнфор Record Настройки
StringConnect Text 1024
isExit Boolean
КолПопыток Integer
DW Dialog
}
RANDOMIZE();
ФирмаИнфор.GET;
IF NOT ФирмаИнфор."Проверка Блокировки до Учета" THEN EXIT;
StringConnect:='driver={SQL Server};server='+Сервер+';uid='+IdUser+';pwd='+Password+';database='+ИмяБазы;
IF GUIALLOWED THEN DW.OPEN(
'Проверка возможности учета\'+
'Попытка: ##1##############',
КолПопыток
);
// инициализация библиоктеки ADO
IF ISCLEAR(ADODB) THEN
IF NOT CREATE(ADODB) THEN ERROR('Не могу инициализировать библиотеку ADO 2.8!');
IF ISCLEAR(ADOREC) THEN
IF NOT CREATE(ADOREC) THEN ERROR('Не могу инициализировать библиотеку ADO 2.8!');
ADODB.CommandTimeout(3600);
ADODB.ConnectionString :=StringConnect;
ADODB.Open();
isExit:=FALSE;
КолПопыток:=0;
WHILE NOT isExit DO
BEGIN
КолПопыток:=КолПопыток+1;
IF GUIALLOWED THEN DW.UPDATE();
//получаем количество блокированных сессий
ADOREC:=ADODB.Execute(
' select count([Connection Id]) [Block Count] '+
' from dbo.[Session] '+
' where '+
' [Database Name] in ('''+ИмяБазы+''') '+
' and [Application Name] = ''Microsoft Business Solutions-Navision Client'' '+
' and [Blocked]<>0 ');
IF NOT ADOREC.BOF THEN
IF NOT ADOREC.EOF THEN
BEGIN
ADOREC.MoveFirst;
END;
Result:=ConvertToInt(ADOREC.Fields.Item(0).Value)>0;
IF МаксПопыток>=0 THEN
BEGIN
IF КолПопыток>=МаксПопыток THEN isExit:=TRUE;
END
ELSE
BEGIN
IF NOT Result THEN isExit:=TRUE;
END;
ADOREC.Close();
IF NOT isExit THEN SLEEP(RANDOM(1000));
END;
ADODB.Close();
IF GUIALLOWED THEN DW.CLOSE();
Данную функцию следует вызывать перед самой передачей документа в codeunit учета, до того как возникнет блокировка учета. Как это работает:
Пользователь 1 запустил учет. Т.к. блокировки нет, он будет учитывать документ.
Пользователь 2 запустит учет. Т.к. блокировки нет, он запустит учет и будет ждать пользователя 1. Зависнет учет на первой строке документа. Количество блокировок 1.
Пользователь 3 видит окно с сообщением «Проверка возможности учета ХХХ» до тех пор, пока пользователь 1 будет блокировать учет пользователя 2, либо не закончится счетчик попыток учета. Пауза между проверками случайным образом будет варьироваться в диапазоне от 0 до 1000 мсек. Когда пользователь 1 закончит учет пользователь 2 начнет учет, а пользователь 3 станет за ним в очередь учета. У меня количество попыток учета 500.
Замечу, что такой подход бережёт ресурсы сервера, т.к. сокращается количество блокировок, которые являются достаточно ресурсоемкими.
Универсальный справочник
У меня всегда вызывает раздражение тот факт, что в Navision считают, что для отображения бизнес информации достаточно тех справочников, которые изначально там присутствуют. Если Вам нужно классифицировать товар по способу хранения, то где хранить справочник способов хранения? Если клиенты делятся по каналу реализации, то где хранить справочник каналов реализации? Причем замечу, что работа несложная, но нужно купить таблицу, форму для каждого справочника. Давайте Купим! Нет, не наш метод.
Как быть. Тут нам придется принять тот факт, что в справочники нужно вносить изменения, а значит, необходима форма. Когда будем разбираться с тем, как можно сэкономить на формах, то у Вас, возможно, она появиться без особых проблем. А вот таблицу нам придётся купить. Но не стоит покупать столько таблиц, сколько справочников нам нужно сейчас или возможно понадобиться в будущем. Давайте ограничимся одной таблицей и одной формой.
Начнем со структуры таблицы.
Универсальный справочник
Поле |
Тип данных | Описание |
Тип строки |
Option/Integer/Code[30] | Это тип справочника. Поле лучше не показывать в форме.
|
Код |
CODE[10] | Ключ справочника |
Наименование |
Text[50] | Описание ключа |
Другие поля |
Если нужны дополнительные поля, то можете их использовать. |
Первичный ключ справочника «Тип строки», «Код». Это гарантирует нам уникальность введенных значений. Если значения справочника нужно сортировать каким-то уникальным способом, то в другие поля можно добавить поле «Сортировка» и создать еще один ключ «Тип строки», «Сортировка», «Код».
Теперь делаем форму.
Нужно учесть, что она должна показывать разные справочники.
Для передачи информации о том какой справочник нужно показать можно использовать фильтр который передаётся при вызове формы. Например, при вызове через lookup.
onLookup()
УниверсальныйСправочник.FILTERGROUP(2);
УниверсальныйСправочник.SETRANGE(«Тип строки»,'БАЛАНС_АО');
УниверсальныйСправочник.FILTERGROUP(0);
IF FORM.RUNMODAL(0, УниверсальныйСправочник) = ACTION::LookupOK THEN
BEGIN
…………..
END;
В форме на onOpenForm нужно добавить показ колонок:
IF ПарТип = 'ОТДЕЛ' THEN
BEGIN
CurrForm.Тип.VISIBLE:=FALSE;
CurrForm.Код.VISIBLE:=FALSE;
CurrForm.Параметр.VISIBLE:=FALSE;
CurrForm.Сортировка.VISIBLE:=TRUE;
End
Else IF ПарТип = 'БАЛАНС_АО' THEN
BEGIN
CurrForm.Тип.VISIBLE:=FALSE;
CurrForm.Код.VISIBLE:=TRUE;
CurrForm.Параметр.VISIBLE:=FALSE;
CurrForm.Сортировка.VISIBLE:=FALSE;
End;
Либо можно в форме создать функцию, в которую нужно передать параметр с названием типа справочника. Я предпочитаю такой вызов. Хотя приходится добавлять переменные формы, но зато через функцию можно передать дополнительные параметры. Например, способ сортировки.
Формы
Показ одной формой данных из разных таблиц
Этот метод хорошо подойдет для справочников. Если у вас есть много таблиц с разной справочной информацией, то не обязательно создавать разные формы для разных справочников. Можно использовать одну форму и временную таблицу, куда можно загружать разные данные. Этот метод подходит и для показа информации из разных источников (ADO,XML и т.д.). Только вот проблема будет с выбором временной таблицы. Она должна иметь достаточно большое количество полей для показа разнообразной информации.
Тема использования временной таблицы в форме хорошо освещена в данном источнике http://naviart.ru/temp-subform-source.
Используя методы универсального справочника из предыдущей главы можно показывать информацию из разных таблиц/источников данных.
Разработка новых документов
Обязательно настанет день, когда у вас появиться задание на создание нового документа в Navision. Давайте посчитаем, сколько таблиц и форм нужно использовать по стандарту:
- Четыре таблицы – заголовок неучтенных документов, строки неучтенных документов, заголовок учтенных документов, строки учтенных документов
- Шесть форм – список неучтенных документов, список учтенных документов, шапка неучтённого документа, шапка учтенного документа, субформа неучтенных строк , субформа учтенных строк.
- Таблица движений – нужна если документы должны изменять состояние учета.
Открываем прайс и считаем, сколько стоят все эти объекты. Давайте купим! Нет не наш метод.
Зачем нам отдельные таблицы для учтенных и неучтенных документов? Есть мнение что хранение в разных таблицах улучшает производительность . Вполне корректное утверждение. Но ведь есть еще такая замечательная возможность SQL сервера как секционирование, т.е. разделение большой таблицы на несколько частей. Если количество Ваших документов станет очень большим, то Вы можете разделить таблицу на секции, не изменяя код Navision отвечающий за заполнение таблицы или извлечение из нее данных. Причем сделать это можно именно тогда, когда большая таблица будет существенно влиять на скорость работы. Кстати советую секционировать такие таблицы как неучтенные документы продажи.
Итак, вместо четырех таблиц можно использовать две. Предположим, нам нужно создать несколько разных документов в рамках одного проекта. Например, учет тары. Нужно ли создавать новые таблицы для каждого вида документа. Скорее всего, нет.
Заголовок
Поле | Тип данных | Описание |
Тип документа | Option | Первичный ключ.Используйте только в том случае, если в одной таблице будет храниться несколько видов документов. Например: Приход тары, Расход тары, Списание тары и т.д. |
Документ Но. | Code[20] | Первичный ключ |
Учтен | Boolean | Если планируется секционирование по учтенным/неучтенным документам, то нужно включить это поле в первичный ключ. |
Другие поля |
Один совет, если в таблице будут храниться разные типы документов, то секционируйте таблицу по типам документов. Секционировать по учтенным/неученным документам в таком случае нецелесообразно. Соответственно поле «Учтен» не нужно включать в первичный ключ. Булевы поля в индексах не самое лучшее решение.
Строки
Поле | Тип данных | Описание |
Тип документа | Option | Первичный ключ.Используйте только в том случае, если в одной таблице будет храниться несколько видов документов. Например: Приход тары, Расход тары, Списание тары и т.д. |
Документ Но. | Code[20] | Первичный ключ |
Строка Но. | Integer | Первичный ключ |
Учтен | Boolean | Если планируется секционирование по учтенным/неучтенным документам, то нужно включить это поле в первичный ключ. В строках это поле нужно только при секционировании, иначе просто исключайте его из структуры таблицы. |
Другие поля |
Т.к. у нас нет разных таблиц для учтенных и неучтенных документов, то мы сразу же можем сократить количество форм до трех: Список документов, Заголовок документа, Субформа строк документа. Это же позволяет нам сократить количество печатных форм документа.
Давайте сначала посмотрим на список документов.
Разделение по типам документа лучше всего реализовать через передачу параметра в форму. Создайте в форме глобальную переменную:
Создайте функцию
SetDocType(pDocType:integer);
Теперь нужно отфильтровать при открытии формы
onOpenForm()
Setrange(“Тип документа”,DocType);
FILTERGROUP(0);
Для корректного вызова формы с нужным типом документа нужно объявить переменные и вызов функции SetDocType()
onPush
ФормаСписка :Form «Спиок документов»
ЗаголовокДокумента: Record “Заголовок”
}
Clear(ФормаСписка);
ФормаСписка.SetDocType(ЗаголовокДокумента.”Тип Документа”::”Приход тары”);
ФормаСписка.Run;
Если пользователю нужно отдельно показывать учтенные и неучтенные документы, то можно добавить параметр «Учтен» в функцию SetDocType() и переделать OpenForm.
Теперь давайте посмотрим на карточку документа.
Нам необходимо запретить редактирование учтенных документов
OpenForm()
Как быть, если у разных типов документов должны быть показаны разные поля. Как правило, в рамках одной задачи поля одинаковые у разных типов документов. Но бывают поля, которые характерны только для одного типа документа. Например, в списании тары – тип списания. Сначала в заголовок формы нужно добавить все поля и изменить OpenForm()
Не забудьте изменить свойство Name=”ТипСписания” у того объекта, который хотите скрыть/показать.
В строках документа Вам, возможно, нужно будет скрывать/показывать разные поля для разных типов документов. Делается это также как и в заголовке только для столбцов субформы.
Шапка документа и субформа. Две формы или одна?
Но вот тот вопрос, который у меня возник, когда я начал писать эту статью. Почему нам необходимо делить форму документа на шапку и строки, используя для этого форму и субформу? Нельзя ли обойтись одной формой? Оказалось можно.
Создайте форму для строк документа. Объявите глобальную переменную:
Установить все необходимые фильтры в OpenForm() для показа одного документа, например:
OpenForm()
//тоже самое для строк
SETRANGE(“Документ Но.”,”ДОК000001”);
Если планируется редактирование заголовка, то в событии onCloseForm добавьте
Добавьте в форму объект закладки и прижмите его к верхнему краю. Табличную часть разместите ниже. Добавьте в закладки поле для редактирования. В свойстве SourceExpr укажите ЗаголовокДокумента.ПолеДляРедактирования. Откомпилируйте форму.
Теперь откройте форму. Успех? Вроде да. Однако при изменении в заголовке документа Вы увидите, что после повторного открытия формы содержимое заголовка и строк не изменилось.
Нужно вызвать Validate() поля. В событии onValidate() поля редактирования нужно написать
onValidate()
Компилируем. Редактирование приводит к желаемому результату. Теперь изменения вносятся в заголовок, и отрабатывает последующий код.
Но как же без ложки дегтя. Проблема в том, что у заголовка нет объекта xRec, т. к. транзакция не начата. Это означает что если в Validate поля, которое Вы редактируете, есть конструкция вида:
То система ничего не сделает т.к. Rec и xRec одинаковы.
Что можно сделать? Есть два варианта:
- Исключить сравнение Rec и xRec в коде Validate как таковое
- Если, все таки, этого не избежать, то
- Выделить сравнение в отдельную функцию
- В заголовке формы создать временную таблицу «xЗаголовокДокумента:Record “Заголовок”;» в которую записывать копию заголовка при открытии формы
- Передавать в функцию сравнения ЗаголовокДокумента и старую копию заголовка для корректного сравнения данных.
В любом случае данную методику можно как минимум использовать для показа учтенных документов, в которые не нужно вносить изменения.
Печать
Самый простой способ экономить в печати — использовать Excel. Я думаю каждый способен выбрать для себя как лучше всего его использовать . Кто-то будет писать прямой код, кто-то использует Query объект, кто-то шаблоны. Я использую все три метода в зависимости от типа задачи.
С точки зрения экономии объектов Report наиболее перспективной является печать документов. У меня в Navision для печати накладной документов продаж используется три отчета: Печать неучтенных продаж/кредит-нот, Печать учтенных продаж, Печать учтенных кредит нот. Если бы в Navision не было деления на учтенные и неучтенные таблицы, то и проблем бы не было. А так с точки зрения администрирования это полный кошмар. Нужно добавить новую колонку, изменить текст, подвинуть поле — нужно вносить изменения в три отчета, в которых еще и некоторые переменные и поля названы по разному.
Зачем нам три отчета для печати одного документа, даже если источники данных разные? Нужно сразу делать один отчет. Отчет должен базироваться на временных таблицах. Я использую временные таблицы на основе неучтенных документов и копирую в них печатаемые документы как учтенные, так и неучтенные. Таким образом, вместо трех отчетов я получаю один.
Однако все равно остается проблема добавления/удаления колонки в секциях Navision. В том же документе продажи у меня 21 секция для печати разных вариантов строк. Поэтому следующим этапом становиться печать документа в Excel с использованием шаблона.Это уже не предмет изучения данной статьи.
В качестве примера как во временную таблицу скопировать разные типы документов приведу пример функции:
//0 - Неучтенные заказ продажи или кредит нота
//1 - Учтенный заказ продажи
//2 - Учтенная кредит нота
{
Name DataType Subtype Length
ПЗ Record Продажа Заголовок
ПС Record Продажа Строка
ПСЗ Record Продажа Счет Заголовок
ПСС Record Продажа Счет Строка
КНЗ Record Продажа Кредит Нота Заголовок
КНС Record Продажа Кредит Нота Строка
}
IF ТипИсточникаДанных=0 THEN
BEGIN
ПЗ.SETFILTER("Тип Документа",'%1|%2',ПЗ."Тип Документа"::Заказ,ПЗ."Тип Документа"::"Кредит Нота");
ПЗ.SETRANGE("Счет Но.",СчетНо);
IF ПЗ.FINDSET THEN
BEGIN
TempHead.INIT;
TempHead.TRANSFERFIELDS(ПЗ);
TempHead.INSERT;
END
ELSE ERROR('Не найден неучтенный документ %1',СчетНо);
ПС.SETRANGE("Тип Документа",ПЗ."Тип Документа");
ПС.SETRANGE("Документ Но.",ПЗ."Но.");
IF ПС.FINDSET THEN
REPEAT
TempLine.INIT;
TempLine.TRANSFERFIELDS(ПС);
TempLine.INSERT;
UNTIL ПС.NEXT=0;
END
ELSE IF ТипИсточникаДанных=1 THEN
BEGIN
ПСЗ.SETRANGE("Но.",СчетНо);
IF ПСЗ.FINDSET THEN
BEGIN
TempHead.INIT;
TempHead.TRANSFERFIELDS(ПСЗ);
TempHead."Тип Документа":=TempHead."Тип Документа"::Заказ;
TempHead.INSERT;
END
ELSE ERROR('Не найдена учтенная продажа %1',СчетНо);
ПСС.SETRANGE("Документ Но.",СчетНо);
IF ПСС.FINDSET THEN
REPEAT
TempLine.INIT;
TempLine.TRANSFERFIELDS(ПСС);
TempLine."Тип Документа":=TempHead."Тип Документа";
TempLine.INSERT;
UNTIL ПСС.NEXT=0;
END
ELSE IF ТипИсточникаДанных=2 THEN
BEGIN
КНЗ.SETRANGE("Но.",СчетНо);
IF КНЗ.FINDSET THEN
BEGIN
TempHead.INIT;
TempHead.TRANSFERFIELDS(КНЗ);
TempHead."Тип Документа":=TempHead."Тип Документа"::"Кредит Нота";
TempHead.INSERT;
END
ELSE ERROR('Не найдена учтенная кредит нота продажи %1',СчетНо);
КНС.SETRANGE("Документ Но.",СчетНо);
IF КНС.FINDSET THEN
REPEAT
TempLine.INIT;
TempLine.TRANSFERFIELDS(КНС);
TempLine."Тип Документа":=TempHead."Тип Документа";
TempLine.INSERT;
UNTIL КНС.NEXT=0;
END
ELSE ERROR('Неверный тип источника данных.');
Приложение
XML
Идея в том, чтобы имитировать работу в стиле ini файла как в Delphi используя XML в качестве хранилища данных. В тему этой статьи: достаточно быстро данный код можно переделать для временной таблицы.
В любом доступном объекте создайте глобальную переменную
Это объект для хранения XML файла. Его нужно правильно инициализировать и удалить с помощью следующих функций:
tXML_Init()
Name DataType Subtype Length
Node Automation 'Microsoft XML, v2.6'.IXMLDOMNode
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
}
CREATE(XMLDoc,TRUE);
Node:=XMLDoc.createProcessingInstruction('xml', 'version=''1.0'' encoding=''UTF-8''');
XMLDoc.appendChild(Node);
Node := XMLDoc.createElement(‘INI’);
XMLDoc.appendChild(Node);
tXML_Free()
Добавляем функции для работы с ключами:
tXML_KeyCount() result : Integer
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
NodeList Automation 'Microsoft XML, v2.6'.IXMLDOMNodeList
}
result:=0;
Doc := XMLDoc.documentElement;
IF NOT ISCLEAR(Doc) THEN
BEGIN
NodeList := Doc.childNodes;
result := NodeList.length;
END;
tXML_KeyExists(Key : Code[30]) result : Boolean
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
Node Automation 'Microsoft XML, v2.6'.IXMLDOMNode
}
Doc := XMLDoc.documentElement;
result := NOT ISCLEAR(Doc);
IF result THEN
BEGIN
Node := Doc.selectSingleNode(Key);
result := NOT ISCLEAR(Node);
END;
tXML_AddKey(Key : Code[30])
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
Node Automation 'Microsoft XML, v2.6'.IXMLDOMNode
}
IF NOT tXML_KeyExists(Key) THEN
BEGIN
Doc := XMLDoc.documentElement;
Node := XMLDoc.createElement(Key);
Doc.appendChild(Node);
END;
tXML_DelKey(Key : Code[30])
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
Node Automation 'Microsoft XML, v2.6'.IXMLDOMNode
}
IF tXML_KeyExists(Key) THEN
BEGIN
Doc := XMLDoc.documentElement;
Node := Doc.selectSingleNode(Key);
Doc.removeChild(Node)
END;
tXML_GetKeyByIndex(Index : Integer) Key : Code[30]
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
NodeList Automation 'Microsoft XML, v2.6'.IXMLDOMNodeList
Node Automation 'Microsoft XML, v2.6'.IXMLDOMNode
}
//Индекс начинается с 0
Key:='';
Doc := XMLDoc.documentElement;
IF NOT ISCLEAR(Doc) THEN
BEGIN
NodeList := Doc.childNodes;
Node := NodeList.item(Index);
Key := Node.nodeName;
END;
tXML_FieldCount(Key : Code[30]) result : Integer
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
KeyNode Automation 'Microsoft XML, v2.6'.IXMLDOMNode
NodeList Automation 'Microsoft XML, v2.6'.IXMLDOMNodeList
}
result:=0;
Doc := XMLDoc.documentElement;
KeyNode := Doc.selectSingleNode(Key);
IF NOT ISCLEAR(KeyNode) THEN
BEGIN
NodeList := KeyNode.childNodes;
result := NodeList.length;
END;
И еще функции для работы со значениями:
tXML_FieldExists(Key : Code[30];Field : Code[30]) result : Boolean
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
KeyNode Automation 'Microsoft XML, v2.6'.IXMLDOMNode
FieldNode Automation 'Microsoft XML, v2.6'.IXMLDOMNode
}
result:=FALSE;
Doc := XMLDoc.documentElement;
KeyNode := Doc.selectSingleNode(Key);
IF NOT ISCLEAR(KeyNode) THEN
BEGIN
FieldNode := KeyNode.selectSingleNode(Field);
result:=NOT ISCLEAR(FieldNode);
END;
tXML_SetField(Key : Code[30];Field : Code[30];Value : Text[1024])
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
KeyNode Automation 'Microsoft XML, v2.6'.IXMLDOMNode
FieldNode Automation 'Microsoft XML, v2.6'.IXMLDOMNode
}
Doc := XMLDoc.documentElement;
KeyNode := Doc.selectSingleNode(Key);
IF ISCLEAR(KeyNode) THEN
BEGIN
tXML_AddKey(Key);
KeyNode := Doc.selectSingleNode(Key);
END;
FieldNode := KeyNode.selectSingleNode(Field);
IF ISCLEAR(FieldNode) THEN
BEGIN
FieldNode := XMLDoc.createElement(Field);
KeyNode.appendChild(FieldNode);
END;
FieldNode.text:=Value;
tXML_GetField(Key : Code[30];Field : Code[30]) result : Text[1024]
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
KeyNode Automation 'Microsoft XML, v2.6'.IXMLDOMNode
FieldNode Automation 'Microsoft XML, v2.6'.IXMLDOMNode
}
result:='';
Doc := XMLDoc.documentElement;
KeyNode := Doc.selectSingleNode(Key);
IF NOT ISCLEAR(KeyNode) THEN
BEGIN
FieldNode := KeyNode.selectSingleNode(Field);
IF NOT ISCLEAR(FieldNode)
THEN result:=FieldNode.text;
END;
tXML_DelField(Key : Code[30];Field : Code[30])
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
KeyNode Automation 'Microsoft XML, v2.6'.IXMLDOMNode
FieldNode Automation 'Microsoft XML, v2.6'.IXMLDOMNode
}
Doc := XMLDoc.documentElement;
KeyNode := Doc.selectSingleNode(Key);
IF NOT ISCLEAR(KeyNode) THEN
BEGIN
FieldNode := KeyNode.selectSingleNode(Field);
IF NOT ISCLEAR(FieldNode)
THEN KeyNode.removeChild(FieldNode);
END;
tXML_GetlFieldByIndex(Key : Code[30];Index : Integer) Field : Code[30]
Name DataType Subtype Length
Doc Automation 'Microsoft XML, v2.6'.IXMLDOMNode
NodeList Automation 'Microsoft XML, v2.6'.IXMLDOMNodeList
Node Automation 'Microsoft XML, v2.6'.IXMLDOMNode
KeyNode Automation 'Microsoft XML, v2.6'.IXMLDOMNode
}
//Index начинается с 0
Field:='';
Doc := XMLDoc.documentElement;
KeyNode := Doc.selectSingleNode(Key);
IF NOT ISCLEAR(KeyNode) THEN
BEGIN
NodeList := KeyNode.childNodes;
Node := NodeList.item(Index);
Field := Node.nodeName;
END;
И функции для сохранения и чтения файлов:
tXML_SaveToFile(FileName : Text[512])
tXML_LoadFromFile(FileName : Text[512])
Функцианальность можно наращивать в зависимости от необходимости.
Пример использования:
Name DataType Subtype Length
tst_XMLTable Record Коды Резервов
}
tst_XMLTable.tXML_Init;
if not tst_XMLTable.tXML_KeyExists(‘A’) then
begin
tst_XMLTable.tXML_AddKey(‘A’);
tst_XMLTable.tXML_SetField(‘A’,’F1’,’test’);
end;
tst_XMLTable.tXML_Free;

Автор: Владимир Викол
Количество статей, опубликованных автором: 5. Дополнительная информация об авторе появится вскоре.
Xml вместо темповой таблицы — тема.
>Но в Navision нет возможности создать временную таблицу произвольной структуры.
Если рекод темповый — платформа не проверяет лицензию на него.
Вполне рабочий юзкейс — создать таблицу с необходимой структурой вне лицензированного клиентом диапазона, и уже там где нужно объявить темповый рекорд на основе этой таблицы и успешно с ним работать.
В каждом отдельном случае
имплантат прочно вживается в
кость от 3 до 6 месяцев, после чего
начинается завершающий этап лечения — протезирование на имплантатах.
Аллотрансплантация (кость берется у подходящего по иммунным
параметрам донора). Аутотрансплантация (недостающий объем кости
берется из другого участка).
Обычно такая потребность возникает у
тех пациентов, которым зуб удалили давно и за это время объем кости успел значительно уменьшиться.
Если костная ткань имеет достаточный объем и десна
в хорошем состоянии, то современная имплантология позволяет устанавливать имплантат сразу после удаления зуба.
После потери верхних зубов
костная ткань дна гайморовой пазухи атрофируется иногда до 1 мм кости.