Правильная ссылка на эту страницу
http://az-design.ru/Support/DataBase/002b4200.shtml

Программа подготовки комплексов VAZKompl

Архангельский Андрей

English

       В двух таблицах можно хранить не только одно дерево, но и множество деревьев, которые ссылаются друг на друга.
       Интересный пример множественных деревьев возник при описании работ для ремонта автомобилей. Реализация была выполнена в программе VAZKompl для разработки комплексов работ для ремонта АМТС и их нормативов.

       Итак! Есть набор комплексов работ для ремонта АМТС, каждый комплекс может состоять из других комплексов из этого же набора. При этом операции, входящие в комплекс могут быть двух видов — "дополнительные", т.е. — это условие при котором комплекс может быть выполнен, и "включенные", т.е. — это то из чего собственно и состоит указанный комплекс. Глубина вложенности комплексов может быть любой.
       Комплексы существуют в двух вариантах:
       — Древовидное представление, предназначенное для разработки комплексов.
       — "Плоское (одномерное) представление", которое содержит список всех операций, входящих в комплекс (список раскрывается рекурсивно) с удалением повторяющихся операций и изменением признаков в соответствии с некоторыми правилами.
       При преобразовании "древовидного представления" в "плоское", которое в данном случае названо "компиляцией", возможны проблемы — если первая операция ссылается на вторую, а та в свою очередь на ссылается на первую, то возникает бесконечный цикл, определить который можно только по исключению генерируемому процедурой.

       Кстати, множество комплексов, разработанных заводами ВАЗ и ГАЗ имеют такие бесконечные циклы, а значит расчеты трудоемкости работ выполненные по заводским нормативам не верны в принципе.

       Правила для преобразования следующие:
       — если "дополнительная" операция на любом уровне имеет "включенную", то эта "включенная" и все вложенные получают признак "дополнительная".
       — если операция изначально имеет признак "включенная", то все вложенные в нее операции сохраняют свой признак.
       — если среди "включенных" операций встречаются две одинаковые на разных уровнях, то оставляется операция с максимальным уровнем, а остальные удаляются.
       — "дополнительные" операции для расчета нормативов используются только с первым уровнем.

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

       При отображении операций — "дополнительные" операции выносяться за комплекс (как условие для выполнения) и оказываются на одном уровне с текущим комплексом; "включенные" операции показаны как "потомки" текущего комплекса. Причем если на этом уровне уже есть такая же "дополнительная" операция, то на дереве показывается только одна.


Рис.4-06 Построение комплекса

       На Рис.4-6 показано как комплекс создавался — 1 "дополнительная" операция (показано коричневым цветом), две "включенные" операции (показано зеленым цветом). Сам комплекс показан темно-синим цветом. "Дополнительная" операция вынесена за пределы комплекса, так как это условие, при котором комплекс может быть выполнен.
       И "дополнительная", и одна из "включенных" операций сами состоят из других операций. Если раскрыть все цепочки вложенных операций, то получиться следующее:


Рис.4-07 Раскрытие комплекса

       Показанный на Рис.4-06 комплекс "10031..." раскрывается следующим образом:
       — для комплекса "10031..." имеется "дополнительная" операция "10007...", которая в свою очередь имеет две "дополнительные" операции "00206..." и "00210..." и три "включенные" — "00562...", "00560...", "00559...".
       — из двух "включенных" одна имеет в свою очередь "дополнительную" операцию "10007...", которая также раскрывается в две "дополнительные" операции "00206..." и "00210..." и три "включенные" — "00562...", "00560...", "00559...".
       После применения всех правил, которые были описаны выше, оказывается, что комплекс состоит только из двух "включенных" операций "10088..." и "10086...", которые на Рис.4-07 выделены светло-зеленым прямоугольником. Все остальные операции оказываются "дополнительными"
       Кроме того, из Рис.4-07 видно, что в результате раскрытия цепочек появляются множество повторяющихся операций, которые названы "пересекающиеся". Из "пересекающихся" операций необходимо оставить только одну, а остальные удалить.
       Сама программа "Подготовки комплексов ВАЗ", несмотря на узкое применение, довольно насыщенная.
       Вкладка "Connect" — кроме подключения к БД, обеспечивает выбор модельного ряда для разработки комплексов. Кроме того, на этой вкладке вызываются процедуры, которые генерируют SQL-скрипты для заполнения целевой БД "PriceService".
       Вкладка "Таблица" — позволяет выбрать различные комплексы для работы — Все, завершенные, незавершенные, по номеру группы, проблемные.
       Вкладка "Комплекс Редактирование" — основной инструмент редактирования выбранного комплекса. На ней можно редактировать как описание комплекса, так наполнение комплекса операциями.
       Вкладка "Материалы" — позволяет добавить к комплексу применяемые материалы типа охлаждающей жидкости или моторного масла.
       Вкладка "SQL" — простой редактор запросов, больше предназначен для тестирования программы, чем для разработчиков комплексов.
       Вкладка "Комплекс Результат" — позволяет посмотреть откомпилированный (плоский) комплекс в различных разрезах — пересекающиеся, удаленные, остающиеся и т.п. операции.
       Вкладка "Тестирование" — позволяет протестировать взаимодействие нескольких комплексов, так как они были бы выбраны в программе "PriceService".
       В данном случае наиболее интересны вкладки "Комплекс Редактирование" и "Комплекс Результат".

"Комплекс редактирование"

       Эта вкладка очень насыщена, потому что нужна вся эта информация одновременно. Под ToolBar с кнопками находится группа полей, которая раскрывает запись о комплексе из таблицы, которая выбрана на вкладке "Таблица". Так как подписи не вмещались, то все названия полей перенесены в хинты, которые можно включит/выключить с помощью чек-бокса в правом верхнем углу.
       В середине формы таблица комплексов, которые могут быть добавлены в редактируемый комплекс. Следовательно, как только новый комплекс будет добавлен в редактируемый, он исключается из этой таблицы. Поиск в этой таблице может быть осуществлен как последовательный, по тексту в поле "Поиск", так и по фильтру, по тексту в поле "Фильтр". Также обеспечена сортировка по основным столбцам.


Рис.4-08 Программа "ВАЗ-Комлекс" — Вкладка "Комплекс Редактирование"

       И, наконец, главное на этой вкладке — это дерево комплексов. Темно-синим цветом окрашет текущий комплекс. Для редактирования эксперт читает описание комплекса ("снять воздушный фильтр..."), находит в в таблице соответствующий комплекс и нажимает кнопку "Доп." или "Включ.", для того чтобы вставить этот комплекс как "Дополнительную" или "Включенную" операцию соответственно. Естественно, что вставляются только те комплексы, которые непосредственно входят в редактируемый комплекс. Посмотреть из чего состоят вставленные комплексы можно на этом же дереве, раскрыв соответствующий узел.
       Для компиляции комплекса нужно нажать кнопку "Компл.". При нажатии на правую кнопку мыши, на кнопке "Компл." появляется меню, позволяющее откомпирировать все комплексы, выбранные на вкладке "Таблица". При этом ошибочные комплексы, которые создают бесконечные циклы, будут перечислены в специальном файле протокола.
       Кнопка с дискеттой "Save" позволяет сохранить комплекс в различных форматах — SQL или Отчет для включения в документацию в формате rtf.
       После завершения работы с комплексом можно нажать кнопку "Finish" и пометить его как "Завершенный"

"Комплекс результат"

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


Рис.4-09 Программа "ВАЗ Комплекс" — вкладка "Комплекс Результат"

       Таблица показывает перечень всех операций, которые получились после рекурсивного раскрытия дерева, с указанием норматива комплекса, нормативов дополнительных и включенных операций, типа операции (удаленные, пересекающиеся, остающиеся), уровня операции в дереве. Поля сверху суммируют значения по соответствующему столбцу. Через всплывающее меню различный набор операций — Все, Пересекающиеся, Остающиеся, Удаленные, Остающиеся 1 уровень, Удаленные больше 1 уровня.
       Эксперты придумывали различные способы выбора остающихся операций, чтобы норматив комплекса был бы равен сумме нормативов, входящих в него операций. Этого не получилось, что и привело к краху всего проекта. На самом деле производитель, в данном случае "ВАЗ", должен был разрабатывать нормативы с использованием этой программы, а не брать их с потолка.
       В полной программе "ВАЗ Комплекс", кроме выше перечисленного, есть функции, которые позволяют редактировать наименования деталей - приводить их к единой форме, устанавливать применяемость деталей на АМТС различной комплектации, определять аналоги деталей внутри одного каталога для построения базового каталога завода.

Как это сделано?

       Таким образом поставлены следующие задачи:
       — построить структуру, которая позволяет хранить все связи.
       — отобразить эту структуру в виде дерева
       — редактировать структуру, добавляя операции как дополнительные или включенные.
       — раскрыть всю цепочку в одноуровневый комплекс,
       — удалить возникающие при раскрытии повторяющиеся (пересекающиеся) операции (пометить как удаленные).
       — сгенерировать SQL-скрипты для заполнения таблицы NSKompleks в программе "PriceService".
       Структура таблиц выглядит следующим образом:

Create table VAZKomplList
      (VZTOID        AZInt32 not null Primary key,
       VAZDIR        AZNOTXT, -- Каталог деталей
       VAZTO         AZNOTXT, -- Сборник нормативов
       VZTOGroup     AZNOTXT references VAZTOGroup on update cascade,
       VZNoPos       AZInt32 default 0, -- Поз. комплекса
       VZSubPos      AZInt32 default 0, -- СубПоз. комплекса,
                                        -- если <0 то материалы
       VZKodDetal    AZInt32 default 0,
       VZKodRabot    AZInt32 default 0,
       VZMaterials   AZInt32 references NSMaterialList on update cascade,
       VZMatrNorm    AZNumber,  -- Норма расхода на единицу
       VZMatrQuty    AZNumber,  -- Количество единиц
       VZNumber      AZInt32,
       VZDetalName   AZTitle,   -- Наименование детали
       VZNameDetal   AZTitle,   -- Заголовок комплекса
       VZDescript    AZLegend,  -- Описание комплекса
       VAZNUM        AZNOTXT,
       NSNrmSRC      AZNOTXT,  -- источник норматива (НАМИ, ВАЗ и т.п.)
       VZNTime       DECIMAL(8,2),  KVZNTime   Integer default 1,<
       VZSignFlg     AZInt32 default 0,
       VZThisCond    AZInt32 default 0,
       VZThisKmpl    AZInt32 default 0,
       VZFinish      AZInt32 default 0,
       VZKCnt        AZInt32 default 0, -- количество включенных операций
       KmplRemark    AZLegend,  -- коментарий к комплексу только 
                                -- для разработчиков 28.02.2006
Unique(VAZDIR,VAZTO,VZNoPos,VZSubPos));
Commit;

       Таблица VAZKomplList представляет собой простой список комплексов, включая заголовок (VZDetalName), описание (VZDescript), норматив (VZNTime) и кратность норматива. Таблица также включает различные дополнительные поля, необходимые для экспертов.
       Поле VZSignFlg собирает набор признаков комплексов для построения заключений:

       2 – Работы по ремонту/замене
       4 – Работы по контролю
       8 – Работы по окраске
      16 – Замена детали
      32 – Материал для операции

       Связи между комплексами строятся на дополнительной таблице VAZKomplTree:

Create table VAZKomplTree
      (VZKompl   Integer not null references VAZKomplList on update cascade 
                                                          on delete cascade,
       VZKOper   Integer not null references VAZKomplList on update cascade 
                                                          on delete cascade,
       VZThis    Integer default 0,
       VZCnt     Integer default 0, -- количество операций для VZKOper
       VZSign    Integer default 0,
Primary Key(VZKompl,VZKOper));
Commit;

       Таблица VAZKomplTree описывает связи между комплексом (VZKompl) и входящими в него операциями (VZKOper). Поле VZThis указывает на тип операции — включенная=1, дополнительная=-1. Дополнительное поле VZSign представляет собой набор признаков, которые могут использоваться для различных целей.
       При этом одна и та же операция не может быть одновременно и "включенной", и "дополнительной".
       Триггеры для таблицы VAZKomplList ничем особенным не отличаются, кроме одного:
       — при вставке нового комплекса в таблицу VAZKomplList в таблицу VAZKomplTree вставляется ссылка на комплекс в поле VZKompl и ссылка на пустую операцию - VZKOper=0.
       При удалении комплекса из таблицы VAZKomplList за счет каскадных связей из таблицы VAZKomplTree удаляются все записи, связанные с этим комплексом.

SET TERM !! ;
CREATE TRIGGER VAZKomplList_InsTree FOR VAZKomplList
ACTIVE AFTER INSERT POSITION 2
AS
Declare variable VZKID  Integer;
BEGIN
  VZKID = new.VZTOID;
  Insert into VAZKomplTree(VZKompl,VZKOper) values(:VZKID,0);
END !!
SET TERM ; !!
Commit;

       Однако, при раскрытии дерева потребуется определять количество дочерних элементов. Поле VZKCnt в таблице VAZKomplList будет считать количество включенных операций, потому что только включенные операции будут рассматриваться как дочерние. Поле VZCnt в таблице VAZKomplTree будет считать общее количество операций, связанных с комплексом, но не для выбранного комплекса (VZKompl), а для операций, на которые он ссылается (VZKOper). Соответственно, добавляются триггеры для таблицы VAZKomplTree:

SET TERM !! ;
CREATE TRIGGER VAZKomplTree_Insert FOR VAZKomplTree
ACTIVE BEFORE Insert POSITION 0
AS
BEGIN
   Update VAZKomplTree t SET t.VZCnt=t.VZCnt+1 where t.VZKoper=new.VZKompl;
   If ((new.VZKOper<>0) and (new.VZThis>0))then
   Update VAZKomplList l SET l.VZKCnt=l.VZKCnt+1 where l.VZTOID=new.VZKompl;
END !!
SET TERM ; !!
Commit;
SET TERM !! ;
CREATE TRIGGER VAZKomplTree_Delete FOR VAZKomplTree
ACTIVE After Delete POSITION 0
AS
BEGIN
   Update VAZKomplTree t SET t.VZCnt=t.VZCnt-1 where t.VZKoper=old.VZKompl;
   Update VAZKomplList l SET l.VZKCnt=l.VZKCnt-1 where l.VZTOID=old.VZKompl;
END !!
SET TERM ; !!
Commit;

       Кроме того необходима вспомогательная таблица VAZKomplLev, которая хранит цепочки комплексов с указанием их уровней во время рекурсивного раскрытия комплекса.

Create table VAZKomplLev
      (VLevID    Integer not null primary key,
       VZKompl   Integer not null references VAZKomplList on update cascade 
                                                          on delete cascade,
       VZKOper   Integer not null references VAZKomplList on update cascade
                                                          on delete cascade,
       VZLevel   Integer default 0,
       VZThis    Integer default 0,
       VZSign    Integer default 0);
Commit;

       При построении связей комплекса требуется довольно простые операции — при добавлении в комплекс новой операции нужно в таблицу VAZKomplTree добавить запись связывающую идентификатор текущего комплекса и идентификатор добавленной операции с указанием признака "включенная" (VZThis=1) или "дополнительная" (VZThis=-1). При необходимости можно записать какие-нибудь признаки в поле VZSign.
       Материалы, в данном случае считаются комплексами, у которых субпозиция имеет отрицательное значение и признак VZThis=2.
       Для раскрытия всей цепочки комплексов используется хранимая процедура GetKomplThread:

SET TERM !! ;
Create procedure GetKomplThread(KBase Integer, Kmpl Integer, TBase Integer, bLev Integer)
       returns (Kompl Integer, Oper Integer, rLev Integer, This Integer, Sign Integer) as
Begin
01  For Select VZKompl,VZKOper,VZThis,VZSign from VAZKomplTree
02             where VZKompl=:Kmpl and VZKOper<>0 into :Kompl,:Oper,:This,:Sign
03      do begin
04         rLev = bLev + 1;
05         Kompl = KBase;
06         If (:Tbase = -1) then This = -1; 
07         Suspend;
08         for select  Kompl, Oper, rLev, This, Sign 
09               from GetKomplThread(:KBase,:Oper,:This,:rLev) 
10               into :Kompl,:Oper,:rLev,:This,:Sign
11             do begin
12                Suspend;
13             end
14      end
end !!
SET TERM ; !!

       Процедура имеет следующие входные параметры:
       KBase — идентификатор базового комплекса, в дальнейшем не изменяется и необходим для заполнения поля VZKompl в таблице VAZKomplLev.
       Kmpl — исходный комплекс, от которое начинается расчет цепочки.
       TBase — признак дополнительной операции.
       bLev — начальное значение уровня операции.
       И выходные параметры:
       Kompl — идентификатор базового комплекса. Всегда равен KBase.
       Oper — операция, связанная с комплексом
       rLev — уровень операции, связанной с комплексом, относительно исходного значения (bLev=0)
       This — признак "Включенная"/"Дополнительная"/"Материалы"
       Sign — набор признаков, полученный их таблицы VAZKomplTree.
       Работа процедуры начинается со строки 01, которая запрос с перечнем всех операций, связанных с комплексом. Условие VZKOper<>0 говорит о том, что сам комплекс в результат запроса не включается. Выбранные в запросе поля записываются в соответствующие переменные.
       Затем для каждой строки полученного набора выполняются следующие действия:
       — выходной уровень устанавливается равным на единицу больше чем входной (строка 04).
       — Входной параметр KBase копируется в выходной параметр Kompl, это позволяет сохранить это значение на протяжении всей цепочки (строка 05).
       — Если операция верхнего уровня была дополнительная, то все дочерние операции будут получать признак "Дополнительная" независимо от того какой признак "Дополнительная"/"Включенная" они имеют (Строка 06). Это правило установили эксперты. В противном случае если "Дополнительная" операция имеет в своем составе "Включенные", то они будут добавляться в комплекс, а это не правильно. Эта строка устраняет такое противоречие.
       — Строка 07 Suspend заносит выходные параметры в выходной набор данных.
       — Затем, используя идентификатор операции текущей строки (VZKOper) выполняется поиск дочерних операций для текущей рекурсивно обращаясь к той же процедуре GetKomplThread (строки 08-13).
       Процедура GetKomplThread может вызываться в запросе с оператором Select, но в программе VAZKompl выходной набор данных сразу сохраняется в таблице VAZKomplLev:

Insert into VAZKomplLev(VZKompl,VZKOper,VZLevel,VZThis,VZSign)
Select Kompl,Oper,rLev,This,Sign from GetKomplThread('
              +IntToStr(KomplSel)+','+IntToStr(KomplSel)+',0,0);

       где KomplSel идентификатор выбранного комплекса.
       Однако, пока вся цепочка операций собрана в таблице VAZKomplLev только в виде идентификаторов. Для просмотра и дальнейшего использования необходимо построить одномерную форму комплекса в таблице NSKomplEditVAZ, которая имеет следующий вид:

Create table NSKomplEditVAZ (
    NSCarKompl  AZInt32   not null references VAZKomplList on update cascade
                                                           on delete cascade,
    NSCarOper   AZInt32D0 not null references VAZKomplList on update cascade
                                                           on delete cascade,
    NSCarLevel  AZInt32 default 0 not null,
    NSKomplOrd  AZInt32 default 0 not null,
    NSSumOper   AZInt32D0 references VAZKomplList on update cascade
                                                  on delete cascade,
    NSKSumTime    AZNumber, -- Норматив КОМПЛЕКСА – суммарный
    NSMaterials   AZInt32 references NSMaterialList on update cascade,
    NSMatrNorm    AZNumber,  -- Норма расхода на единицу
    NSMatrQuty    AZNumber,  -- Количество единиц
    NSKomplPos    AZInt32,
    NSKomplSub    AZInt32,
    NSKomplDet    AZInt32,
    NSKomplRab    AZInt32,
    NSOperPos     AZInt32,
    NSOperSub     AZInt32,
    NSOperDet     AZInt32,
    NSOperRab     AZInt32,
    NSOperLev     AZInt32,
    NSOperThis    AZInt32,
    NSSignFlg     AZInt32 default 0, -- набор признаков
    NSKomplCnt    AZInt32 default 1,
    NSKmplTime    AZNumber, -- Норматив КОМПЛЕКСА – суммарный
    NSCondTime    AZNumber, -- Норматив ДОПОЛНИТЕЛЬНОЙ операции
    NSOperTime    AZNumber, -- Норматив ВКЛЮЧЕННОЙ операции
    NSKompType    AZInt32 default 0,
    NSOperType    AZInt32 default 0,
    NSCondType    AZInt32 default 0,
Primary key (NSCarKompl,NSKomplOrd));
Commit;

       Эта таблица по сути взята из основной программы PriceService с некоторыми упрощениями. Для того чтобы результаты тестов не отличались от работы в целевой программе в таблице сохранены многие вспомогательные поля.
       Путем выполнения нескольких запросов в таблице формируется "плоская" форма комплекса:

Insert into NSKomplEditVAZ(NSCarKompl,NSCarOper,NSSumOper,NSOperLev,NSKomplCnt,
       NSCondTime,NSOperPos,NSOperSub,NSOperDet,NSOperRab,NSSignFlg,NSOperThis)
Select KomplSel,v.VZKOper,v.VZKOper,v.VZLevel,l.KVZNTime,l.VZNTime,
       L.VZNoPos,L.VZSubPos,L.VZKodDetal,L.VZKodRabot,L.VZSignFlg,-1
  from VAZKomplLev V, VAZKomplList L
 where L.VZTOID=v.VZKOper and v.VZThis=-1
   and v.VZKompl=KomplSel;

       Этот запрос заполняет таблицу NSKomplEditVAZ нормативами "дополнительных" операций для выбранного комплекса (переменная KomplSel выделена жирным шрифтом). Обратите внимание, что идентификатор операции участвует в запросе дважды — он заполняет поля NSCarOper и NSSumOper. Поле NSSumOper используется для определения пересекающихся операций. Так как точно такая же таблица и соответствующая ей процедура используется в программе PriceService, то в это поле заполняется и идентификаторами самих комплексов. Так как в данном случае комплекс один, то эта возможность не используется.

Insert into NSKomplEditVAZ(NSCarKompl,NSCarOper,NSSumOper,NSOperLev,NSKomplCnt,
       NSOperTime,NSOperPos,NSOperSub,NSOperDet,NSOperRab,NSSignFlg,NSOperThis)
Select KomplSel,v.VZKOper,v.VZKOper,v.VZLevel,l.KVZNTime,l.VZNTime,
       L.VZNoPos,L.VZSubPos,L.VZKodDetal,L.VZKodRabot,L.VZSignFlg,1
from VAZKomplLev V, VAZKomplList L
where L.VZTOID=v.VZKOper and v.VZThis=1
   and v.VZKompl=KomplSel;

       Этот запрос заполняет таблицу NSKomplEditVAZ нормативами "Включенных" операций для выбранного комплекса (переменная KomplSel выделена жирным шрифтом). Поле NSSumOper используется таким же образом.

Update NSKomplEditVAZ Set NSKmplTime=vNTime
 where NSCarKompl=KomplSel;

       После чего поле NSKmplTime (норматив выбранного комплекса) для всех записей заполняется значением из таблицы VAZKomplList.

Insert into NSKomplEditVAZ(NSCarKompl,NSCarOper,NSSumOper,NSOperLev,
                 NSMaterials,NSMatrNorm,NSMatrQuty,
                 NSOperPos,NSOperSub,NSOperDet,NSOperRab,NSSignFlg,NSOperThis)
Select KomplSel,v.VZKOper,v.VZKOper,v.VZLevel,
       l.VZMaterials,l.VZMatrNorm,l.VZMatrQuty,
       L.VZNoPos,L.VZSubPos,L.VZKodDetal,L.VZKodRabot,L.VZSignFlg,2
from VAZKomplLev V, VAZKomplList L
where L.VZTOID=v.VZKOper and v.VZThis=2
  and v.VZKompl=KomplSel;

       После этого в комплекс записываются необходимые материалы.
       Таким образом, сформированный комплекс оказался в таблице NSKomplEditVAZ и требуется только исключить пересекающиеся операции, что выполняется с помощью хранимой процедуры KomplEditCrossFixVAZ:

SET TERM !! ;
create procedure KomplEditCrossFixVAZ(Kompl Integer) as
declare variable Ord       Integer;
declare variable Lev       Integer;
declare variable SumOper   Integer;
declare variable KTime     double precision;
declare variable OTime     double precision;
declare variable CTime     double precision;
BEGIN
  for select NSSumOper from NSKomplEditVAZ where NSCarKompl=:Kompl
      group by NSSumOper Having Count(NSSumOper)>1 into :SumOper
   do begin
      Update NSKomplEditVAZ set NSKompType=-1 where NSSumOper=:SumOper
                                                and NSCarKompl=:Kompl;
      select first 1 NSKomplOrd,NSOperLev,NSKmplTime,NSOperTime,NSCondTime
           from NSKomplEditVAZ
           where NSSumOper=:SumOper and NSCarKompl=:Kompl
          Order by NSOperPos asc, NSKmplTime asc, NSOperTime asc,
                   NSCondTime desc, NSOperLev desc
          into :Ord,:Lev,:KTime,:OTime,:CTime;
      Update NSKomplEditVAZ set NSKompType=1 where NSKomplOrd=:Ord
                                               and NSCarKompl=:Kompl;
    end
END !!
SET TERM ; !! 
Commit;

       Как работает эта процедура?
       Для начала строится запрос, который выбирает идентификаторы операций (независимо от ее типа — комплекс, дополнительная или включенная), которые имеются в таблице NSKomplEditVAZ в количестве больше одной.
       Затем для каждой из этих записей выполняются следующие действия:
       — все записи группы помечаются как удаленные (оператор Update)
       — группа сортируется в определенном порядке, который оговаривался с экспертами и формулирует правила удаления,
       — и первая запись этого запроса помечается 1, как перекрестная и остающаяся.
       Получившийся набор признаков позволяет выбирать из таблицы записи в любой комбинации, что и отображается на вкладке "Комплекс Результат"

Как это отобразить на дереве?

       После того, как таблицы построены и данные сформированы, то нужно каким-то образом отобразить их в древовидной структуре.
       Первая процедура для отображения построенных связей запускается при переходе от одной записи к другой в таблице VAZKomplList, по событию qrTable.onAfterScroll.

procedure TForm1.KomplTreeShow; {Example07}
var
  sFld1,sFld2 : STring;
  x,xs,y,z : Integer;
  NewNode,ChldNode : TTreeNode;
begin
KomplSel := qrTable.FieldValues['VZTOID'];

       Получили идентификатор выбранного комплекса

qrTableBase.Close;   qrTableBase.SQL.Clear;
qrTableBase.SQL.Add('Select * from VAZKomplList where VZTOID not in');
qrTableBase.SQL.Add('(Select distinct VZKOper from VAZKomplTree where VZKompl='
                   +IntToStr(KomplSel)+')');
qrTableBase.SQL.Add(' and VAZDIR='''+sNSDIR+''' and VAZTO='''+sNSTO
                   +''' and VZTOID<>'+IntToStr(KomplSel));
qrTableBase.SQL.Add(' order by VZNoPos,VZKodDetal');
qrTableBase.Open;

       Сформирован запрос для таблицы выбора комплекса. В нем отсутствуют комплексы, которые уже присутствуют в выбранном комплексе. Это действие не относится к построению дерева, но это удобное место для выполнения.

qrKomplEdit.Close;   qrKomplEdit.SQL.Clear;
qrKomplEdit.SQL.Add('Select L.VZTOGroup,L.VZNoPos,L.VZSubPos,L.VZKodDetal,');
qrKomplEdit.SQL.Add(' L.VZKodRabot,L.VZNameDetal,L.VZDescript,L.KVZNTime,');
qrKomplEdit.SQL.Add(' L.VZSignFlg,E.NSKmplTime,E.NSCondTime,E.NSOperTime,');
qrKomplEdit.SQL.Add(' E.NSKompType, E.NSOperLev');
qrKomplEdit.SQL.Add('from NSKomplEditVAZ E, VAZKomplList L');
qrKomplEdit.SQL.Add('Where l.VZTOID=e.NSCarOper and e.NSCarKompl='
                   +IntToStr(KomplSel));
qrKomplEdit.SQL.Add(' and E.NSKompType>=0');
qrKomplEdit.SQL.Add(' order by E.NSKmplTime,E.NSCondTime,E.NSOperTime');
qrKomplEdit.Open;

       Сформирован запрос для отображения результата скомпилированного комплекса с сортировкой по нормативам. Также не имеет отношение к отображению, но необходим для просмотра результата. Этот запрос впоследствии будет изменен по выбору пользователя из меню.
       Построение дополнительных операций верхнего уровня

TVKompl.Items.Clear;  TVKompl.Items.BeginUpdate;
trOutIndex.Active := True;   qrOutIndex.Close;   qrOutIndex.SQL.Clear;
qrOutIndex.SQL.Add('Select L.VZTOID,L.VzNoPos,L.VzSubPos,L.VzKodDetal,');
qrOutIndex.SQL.Add(' L.VzKodRabot, L.VzNameDetal, T.VZCnt ');
qrOutIndex.SQL.Add(' from VAZKomplTree T, VAZKomplList L');
qrOutIndex.SQL.Add('where T.VZKompl='+IntToStr(KomplSel)
                  +' and T.VZThis=-1 and L.VZTOID=T.VZKOper');
   qrOutIndex.Open;

       Запрос выбирает все дополнительные операции, которые связаны с выбранным комплексом.

   While not qrOutIndex.Eof do Begin
      x  := qrOutIndex.FieldValues['VzNoPos'];
      xs := qrOutIndex.FieldValues['VzSubPos'];
      y  := qrOutIndex.FieldValues['VzKodDetal'];
      z  := qrOutIndex.FieldValues['VzKodRabot'];
      sFld1 := LeadZero(x,5) + '.' + LeadZero(y,7) + '.' + LeadZero(z,2);
      If xs>0 then sFld2 := '['+LeadZero(xs,3)+'] '
                  +qrOutIndex.FieldValues['VzNameDetal']
              else sFld2 := qrOutIndex.FieldValues['VzNameDetal'];

       Из набора полей формируется текст узла дерева.

      NewNode := TVKompl.Items.Add(TVKompl.TopItem,sFld1+' - '+sFld2);
      NewNode.ImageIndex := qrOutIndex.FieldValues['VZTOID'];
      NewNode.OverlayIndex := -1;
      If qrOutIndex.FieldValues['VZCnt']>0 
         then ChldNode := TVKompl.Items.AddChild(NewNode,'*');

       Если дополнительная операция имеет дочерние, то создается дочерний узел.

      qrOutIndex.Next; // за следующей строкой
   end;

trOutIndex.Active := False;

       Построение комплекса выполняется как последний узел дерева на этом уровне. Данные берутся из основной таблицы списка комплексов, поэтому никаких дополнительных запросов не требуется.

x  := qrTable.FieldValues['VzNoPos'];
xs := qrTable.FieldValues['VzSubPos'];
y  := qrTable.FieldValues['VzKodDetal'];
z  := qrTable.FieldValues['VzKodRabot'];
sFld1 := LeadZero(x,5) + '.' + LeadZero(y,7) + '.' + LeadZero(z,2);
If xs>0 then sFld2 := '['+LeadZero(xs,3)+'] '
                     +qrTable.FieldValues['VzNameDetal']
        else sFld2 := qrTable.FieldValues['VzNameDetal'];

       Оформляется текст узла. Если это субпозиция, то в текст добавляется номер субпозиции в квадратных скобках

NewNode := TVKompl.Items.Add(TVKompl.TopItem,sFld1+' - '+sFld2);
NewNode.ImageIndex := KomplSel;        NewNode.OverlayIndex := 0;
z := qrTable.FieldValues['VzKCnt'];
If z>0 then ChldNode := TVKompl.Items.AddChild(NewNode,'*');

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

TVKompl.Items.EndUpdate;
TVKompl.Update;
qrKomplSum.Close;   qrKomplSum.SQL.Clear;
qrKomplSum.SQL.Add('Select MAX(NSKmplTime) as KmplTime,'
                  +' Sum(NSCondTime) as SumCondTime,');
qrKomplSum.SQL.Add('Sum(NSOperTime) as SumOperTime');
qrKomplSum.SQL.Add('from NSKomplEditVAZ');
qrKomplSum.SQL.Add('Where NSCarKompl='+IntToStr(KomplSel));
qrKomplSum.SQL.Add(' and NSKompType>=0');
qrKomplSum.Open;
end;

       Раскрытие узла примерно также, как у других деревьев, но есть некоторые особенности и правила:
       — Дополнительные операции должны выводится на тот же уровень, где находится узел.
       — Если на этом уровне уже есть такая же дополнительная операция, то она не выводится. Это связано с тем, что дополнительная операция — это условие, при котором может быть выполнен комплекс. Если условие уже выполнено, то второй раз его выполнять не требуется.
       Как всегда процедура раскрытия узла привязана к событию onExpanding:

procedure TForm1.TVKomplExpanding(Sender: TObject; Node: TTreeNode;
                                     var AllowExpansion: Boolean);
var
  x,xs,y,z,rID,Typ,nID,nTyp : Integer;
  NewNode,ChldNode,TmpNode : TTreeNode;
  DublNode : Boolean;
  sFld1,sFld2 : String;
begin
nID := Node.ImageIndex;  nTyp := Node.OverlayIndex;
trOutTable.Active := True;  qrOutTable.Close;   qrOutTable.SQL.Clear;
qrOutTable.SQL.Add('Select * from VAZKomplTree T, VAZKomplList L');
qrOutTable.SQL.Add('where T.VZKompl='+IntToStr(nID));
qrOutTable.SQL.Add(' and L.VZTOID=T.VZKOper and L.VZTOID<>0');
qrOutTable.SQL.Add(' order by T.VZThis');
qrOutTable.Open;  TVKompl.Items.BeginUpdate;

       Оформлен запрос на выборку всех операций связанных с текущим узлом.

Node.DeleteChildren;  qrOutTable.First;
While not qrOutTable.Eof do Begin
    rID := qrOutTable.FieldValues['VzTOID'];
    Typ := qrOutTable.FieldValues['VZThis'];
    x := qrOutTable.FieldValues['VzNoPos'];
    xs := qrOutTable.FieldValues['VzSubPos'];
    y := qrOutTable.FieldValues['VzKodDetal'];
    z := qrOutTable.FieldValues['VzKodRabot'];
    sFld1 := LeadZero(x,5) + '.' + LeadZero(y,7) + '.' + LeadZero(z,2);
    If xs>0 then sFld2 := '['+LeadZero(xs,3)+'] '
                         +qrOutTable.FieldValues['VzNameDetal']
            else sFld2 := qrOutTable.FieldValues['VzNameDetal'];

       Оформляется текст узла. Если это субпозиция, то в текст добавляется номер субпозиции в квадратных скобках

    If ((Typ=-1) and (nTyp<>0)) then Begin
        TmpNode := Node;  DublNode := False;
        while TmpNode<>nil do begin
           If TmpNode.ImageIndex=rID then DublNode := True;
           TmpNode := TmpNode.getPrevSibling;
        end;
        If not DublNode then begin
           NewNode := TVKompl.Items.AddFirst(Node,sFld1+' - '+sFld2);
           NewNode.ImageIndex := rID;  NewNode.OverlayIndex := Typ;
        end
    end;

       Если это дополнительная операция, то выполняется последовательный перебор всех узлов и если такой операции нет, то создается узел на том же уровне.

    If (Typ>0) then begin
       NewNode := TVKompl.Items.AddChild(Node,sFld1+' - '+sFld2);
       NewNode.ImageIndex := rID;  NewNode.OverlayIndex := Typ;
    end;

       Если это включенная операция, то содается дочерний элемент.

    z := qrOutTable.FieldValues['VZCnt'];
    If ((z>0) and (NewNode<>nil))
       then ChldNode := TVKompl.Items.AddChild(NewNode,'*');

       И если с этой включенной операцией связаны другие операции — включенные или дополнительные — то для нее создается дочериний узел.

    qrOutTable.Next;
end; // While not qrOutTable.Eof do Begin
qrOutTable.Close;   trOutTable.Active := False;
TVKompl.Items.EndUpdate;  TVKompl.Update;
end;

       И, наконец, дерево необходимо расскрасить. Для раскраски используется признак состояния, которых сохранен в свойстве Node.OverlayIndex

procedure TForm1.TVKomplCustomDrawItem(Sender: TCustomTreeView;  Node: TTreeNode; 
                                           State: TCustomDrawState;
                                           var DefaultDraw: Boolean); {Example07}
var
  NodeRect: TRect;
begin
  with TVKompl.Canvas do
  begin
    if cdsSelected in State then
    begin
      Brush.Color := clInfoBk;
      NodeRect := Node.DisplayRect(True);
      TVKompl.Canvas.FillRect(NodeRect);
    end;
    If cdsHot in State then  TVKompl.Canvas.Font.Style := [fsUnderline];
    If Node.OverlayIndex=2  then TVKompl.Canvas.Font.Color := clFuchsia;
    If Node.OverlayIndex=1  then TVKompl.Canvas.Font.Color := clGreen;
    If Node.OverlayIndex=0  then begin TVKompl.Canvas.Font.Color := clNavy;
                                       TVKompl.Canvas.Font.Style := [fsBold]; end;
    If Node.OverlayIndex=-1 then TVKompl.Canvas.Font.Color := clMaroon;
  end;
end;

 

© 01.07.2007, Архангельский А.Г.

<<Пред. Оглавление
Начало раздела
Главная страница
След.>>




Дата последнего изменения:
Thursday, 21-Aug-2014 09:10:44 MSK


Постоянный адрес статьи:
http://az-design.ru/Support/DataBase/002b4200.shtml