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

Расчет трудоемкости ремонта автомобилей

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

English

       Реальное применение сложного дерева возникло при разработке программы расчета трудоемкости ремонта автомобиля по заказу ООО "ПРАЙС-Н" (отдел цен НАМИ). Эта программа намечалась как замена программы "Авто Эксперт 4.5" и "Авто Сервис 4.0".
       Трудоемкость ремонта автомобилей расчитывается как сумма трудоемкостей отдельных операций, которые выполняются над отдельными деталями ремонтируемого автомобиля. При этом стоимость нормо/часа зависит от типа операции (Код работы).
       Итак, в одной таблице Goods(Товары) храняться модели автомобилей, заголовки операций, каталог деталей, список кодов работ, как отдельные ветви товаров. Такая схема хранения позволяет автоматически связать автомобили, детали и работы связать с прайс-листом, складами и другими частями информационной системы предприятия. Это позволяет применять эту же БД в сопутствующих приложениях — Авто-Страхование, Авто-Сервис, Торговля автозапчастями, Торговля автомобилями. Сейчас появилось много фирм, которые совмещают несколько из перечисленных областей. Каждая из этих групп имеет свою структуру. Кроме того, это позволяет уменьшить объем и количество ошибок в таблице Vehicles (Автомобили).
       Комплексом, называется одна или группа операций, которая расматривается экспертом как единое ремонтное воздействие и имеет норматив ремени на выполнение. Например, "Двигатель - снять/установить" — отдельный комплекс, который состоит из множества включенных и дополнительных операций.
       Дополнительная операция — это операция или комплекс, которые необходимо выполнить до выполнения основного комплекса, как условие при котором основной комплекс может быть выполнен.
       Включенная операция — это операция или комплекс, из которых состоит основной комплекс.
       Пересекающаяся операция — это операция или комплекс, которая входит в два или более выбранных комплекса и должна учитываться однократно.
       Задача программы была в том, что после того как эксперт выберет необходимые ремонтные воздействия и будет образован список комплексов.
       Дополнительную проблему составляет то, что каждая операция может представлять из себя комплекс таких же операций. Но для данной программы были подготовленны с помощью специальной программы VAZKompl одномерные комплексы, которые состоят из простого списка операций. Примерное описание программы ПРАЙС-Сервис со скриншотами находится на CD в каталоге Soft.
       Таким образом, для хранения нормативов трудоемкостей на техническое обслуживание используется комбинация из двух таблиц — уже известной таблицы Goods и таблицы NSKompleks:

Create table NSKompleks (
  NSCarModel  Integer not null references Goods on update cascade,
  NSCarDetal  Integer not null references Goods on update cascade,
  NSCarKompl  Integer not null references Goods on update cascade,
  NSCarOper   Integer default 0 not null references Goods on update cascade,
  NSRabotKod  Integer references Goods on update cascade,
  NSKmplTime  AZNumber, -- Норматив КОМПЛЕКСА – суммарный
  NSCondTime  AZNumber, -- Норматив ДОПОЛНИТЕЛЬНОЙ операции
  NSOperTime  AZNumber, -- Норматив ВКЛЮЧЕННОЙ операции
Primary key (NSCarModel,NSCarDetal,NSCarKompl,NSCarOper));
Commit;

       Таким образом данная древовидная структура может быть построена на основании четырех ссылок в другую древовидную структуру. Пятая ссылка (NSRabotKod) не участвует в построении дерева и используется при определении стоимости в рублях — для этого осуществляется переход NSKompleks=>Goods=>Price.
       Пример {Example08IBORz}, который является частью программы PriceService, иллюстрирует построение дерева.
       Так как в реальной программе модель АМТС выбирается в другом диалоге не связанном с этим деревом, то в данном примере взята одна конкретная модель ВАЗ-2109.
       Корень дерева состоит из групп узлов, в том порядке, как это определено в каталоге завода-производителя:


Рис.4-01 Корень дерева для выбора ремонтных воздействий

       При раскрытии узлов дерева каждый узел раскрашивается в определенные цвета в зависимости от назначения узла:
       — узел АМТС — в бордовый цвет,
       — деталь АМТС — в зеленый цвет,
       — ремонтное воздействие (комплекс работ) — в синий цвет,
       как это показано ниже.


Рис.4-02 Выбор ремонтного воздействия в дереве комплексов

       При щелчке мыши на чек-боксе все операции, связанные с выбранным комплексом копируются в промежуточную таблицу NSKomplTemp. При этом если комплексы были выбраны в прошлом сеансе, то ранее выбранные комплексы отмечаются на дереве автоматически. В реальной программе к таблице комплексов подключена таблица применяемости деталей, в результате в дереве остаются только те детали, которые используются на конкретной модели АМТС в конкретной комплектации. Соответственно, количество деталей, отображаемых в дереве существенно меньше.
       Также в реальной программе было построено два варианта дерева (по выбору пользователя) — одно, как показано выше, строится по схеме — Узел->Деталь->Работа, другое строится по схеме — Узел->Работа->Деталь. Кроме того, так как таблица Goods имеет 5 деревьев, то пользователь мог выбрать структуру узлов-деталей в варианте завода-изготовителя или стандартной в автомобильной отрасли. Они далеко не всегда совпадают.
       В случае, если одинаковых деталей несколько, то выдается запрос, для скольких деталей применяется комплекс, как это показано ниже:


Рис.4-03 Выбор нескольких комплексов.

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


Рис.4-04 Отчет (заключение) о стоимости работ по ремонту АМТС

       Так как это тестовый пример, то цена за единицу нормо-часа указана примерная и одинаковая для всех видов работ.
       Кроме того, при переходе на страницу "Таблица результата" выполняется поиск пересекающихся комплексов и они удаляются из результата.
       Так как в примере {Example08IBORz} используется достаточно много коммерческих компонентов, то исходных текстов на CD не содержится. Однако, немного упрощенный пример {Example08} показывает, как построить такое дерево используя только штатные компоненты Delphi 7.

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

       Итак, пример {Example08} не использует чек-боксы, не выбирает комплексы, но при этом строит точно такое же дерево, как предыдущем примере.


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

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

procedure TForm1.FormCreate(Sender: TObject); {Example08}
begin
   sGdCnt   := 'GdCntFrm';   sGdCode := 'GdFirmCod';
   sGdParnt := 'GdPrnFrm';  sNSDetal := 'NSDetalFrm';
   qrExeProc.Close;   qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Select distinct NSCarModel from NSKompleks'
                    +' where NSCarModel<>0');
   qrExeProc.Open;
   iGrpModel := qrExeProc.FieldValues['NSCarModel'];
   qrExeProc.Close;   qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Select GdNames from Goods where GoodsNo='
                     +IntToStr(iGrpModel));
   qrExeProc.Open;
   Form1.Caption := qrExeProc.FieldValues['GdNames'];
   qrExeProc.Close;
   ctvKomplShowRoot;
end;

       Из таблицы NSKompleks выбирается список моделей АМТС, для которых есть нормативы комплексов. Так как это пример, то в этой БД существуют нормативы только для одного модельного ряда и можно взять первую запись присвоить ее значение переменной iGrpModel и оформить Form1.Caption.
       После чего процедура ctvKomplShowRoot начинает строить корень дерева:

procedure TForm1.ctvKomplShowRoot;
Var
   RowCount,RowChild : Integer;
   sFld : String;
   x,z : Integer;
   ChildNode,NewNode : TTreeNode;
begin
  inherited;
If not FirstKompl then Begin
  qrExeProc.Close;    qrExeProc.SQL.Clear;
  qrExeProc.SQL.Add('Select * from NSModelRoot where NSModelFrm='
                   +IntToStr(iGrpModel));
  qrExeProc.Open;
  x := qrExeProc.FieldValues[sNSDetal];

       Для построения корневых узлов требуется получить самые верхние группы узлов деталей, для которых существуют комплексы. В принципе это можно сделать прямо в этой процедуре по методу, описанному в "Получение самого верхнего родителя". Но оказалось что на достаточно больших таблицах время получения "Верхнего родителя" достигает нескольких секунд и более, что создает дискомфорт для пользователя. С другой стороны, эти данные можно сохранить в дополнительной таблице NSModelRoot и заполнять ее при построении БД. В этом случае при построении дерева достаточно выбрать заранее подготовленные данные из таблицы NSModelRoot.

  qrTVKompl.Close;    qrTVKompl.SQL.Clear;
  qrTVKompl.SQL.Add('Select GoodsNo,'+sGdCnt+','+sGdCode
                   +',GdNames from Goods');
  qrTVKompl.SQL.Add(' where '+sGdParnt+'='+IntToStr(x));
  qrTVKompl.SQL.Add(' order by '+sGdCode+',GdNames');
  qrTVKompl.Open;    RowCount := qrTVKompl.RecordCount;
  ctvKompl.Items.BeginUpdate;  ctvKompl.Items.Clear;
  While not qrTVKompl.Eof do Begin
        sFld := qrTVKompl.FieldValues['GdNames'];
        z    := qrTVKompl.FieldValues['GoodsNo'];
        RowChild := qrTVKompl.FieldValues[sGdCnt];
        NewNode := ctvKompl.Items.Add(ctvKompl.TopItem,sFld);
        NewNode.ImageIndex := z;   NewNode.OverlayIndex := 1;
        If RowChild>0 then
           ChildNode := ctvKompl.Items.AddChild(NewNode,IntToStr(RowChild));
        qrTVKompl.Next;
  end;
  ctvKompl.Items.EndUpdate;   ctvKompl.Update;  FirstKompl := True;
End; // FirstFrame
end;

       А дальше выполняется стандартный цикл построения корневых узлов дерева.
       Основные хитрости начинаются при раскрытии узлов в процедуре TForm1.ctvKomplExpanding. Как и в предыдущих случаях для хранения признаков используются ImageIndex и OverlayIndex, которые обычно используются для управления картинками. Таким образом, если в проекте предполагается использовать картинки для узлов, то эти признаки нужно перенести в дополнительную структуру Data. Признаки определяются следующим образом:

      Node.ImageIndex    = GoodsNo - текущей записи (узла)
      Node.OverlayIndex  = 1  - признак того, запись (узел) это группа Деталей
      Node.OverlayIndex  = 0  - признак того, запись (узел) это Деталь
      Node.OverlayIndex  = -1 - признак того, запись (узел) это Комплекс

       После этого необходимо определить структуру дерева:

ГРУППА ДЕТАЛЕЙ               - Верхний уровень группы деталей (всегда)
  +-- ГРУППА ДЕТАЛЕЙ_0       - 0 уровень группы деталей (не всегда)
     +--  ГРУППА ДЕТАЛЕЙ_1   - 1 уровень группы деталей (не всегда)
        +-- ДЕТАЛЬ           - Деталь (всегда)
           +-- КОМПЛЕКС      - Комплекс (всегда)

       На одном уровне может быть группа деталей и одиночная деталь в результате, поэтому раскрытие узлов придется делать в два этапа. В качестве признака, который используется для определения этапа, можно взять Node.OverlayIndex. Если он равен 1, то можно построить группу деталей и детали, которые находятся на одном уровне с группой. Если он равен 0, то это деталь, у которой есть комплексы.

procedure TForm1.ctvKomplExpanding(Sender: TObject; Node: TTreeNode;
                                   var AllowExpansion: Boolean); {Example08}
Var
   rFld      : Double;
   sCod,sFld : String;
   y,z,tn : Integer;
   ChildNode,NewNode,KomplNode : TTreeNode;
begin
  tn := Node.OverlayIndex;
  y := Node.ImageIndex;

       Исходные данные из текущего узла.

  If tn=1 then Begin  // это группа деталей
  qrTVKompl.Close;    qrTVKompl.SQL.Clear;
  qrTVKompl.SQL.Add('Select GoodsNo,'+sGdCode+',GdNames from Goods'
                   +' where '+sGdParnt+'='+IntToStr(y)+' and '+sGdCnt+'>0');
  qrTVKompl.SQL.Add(' order by '+sGdCode);
  qrTVKompl.Open;    qrTVKompl.First;
  Node.DeleteChildren;    ctvKompl.Items.BeginUpdate;
  While not qrTVKompl.Eof do Begin
     sCod := qrTVKompl.FieldValues[sGdCode];
     sFld := qrTVKompl.FieldValues['GdNames'];
     z    := qrTVKompl.FieldValues['GoodsNo'];
     NewNode := ctvKompl.Items.AddChild(Node,sCod+' - '+sFld);
     NewNode.ImageIndex := z;    NewNode.OverlayIndex := 1;
     ChildNode := ctvKompl.Items.AddChild(NewNode,'Работа');
     qrTVKompl.Next;
  end;
  ctvKompl.Items.EndUpdate;   ctvKompl.Update;

       Первый запрос получает группы деталей, которые являются детьми текущего узла и сами имеют потомков (sGdCnt>0). Из них строится первая часть дерева и для ее узлов устанавливается признак "группа деталей"

  qrTVKompl.Close;    qrTVKompl.SQL.Clear;
  qrTVKompl.SQL.Add('Select distinct GoodsNo,'+sGdCode+',GdNames '
            +'from Goods where '+sGdParnt+'='+IntToStr(y)+' and '+sGdCnt+'=0');
  qrTVKompl.SQL.Add(' and GoodsNo in (Select distinct NSCarDetal '
                 +'from NSKompleks where NSCarModel='+IntToStr(iGrpModel)+')');
  qrTVKompl.SQL.Add(' order by '+sGdCode);
  qrTVKompl.Open;    qrTVKompl.First;
  ctvKompl.Items.BeginUpdate;
  While not qrTVKompl.Eof do Begin
     sCod := qrTVKompl.FieldValues[sGdCode];
     sFld := qrTVKompl.FieldValues['GdNames'];
     z    := qrTVKompl.FieldValues['GoodsNo'];
     NewNode := ctvKompl.Items.AddChild(Node,sCod+' - '+sFld);
     NewNode.ImageIndex := z;  NewNode.OverlayIndex := 0;
     ChildNode := ctvKompl.Items.AddChild(NewNode,'Работа');
     qrTVKompl.Next;
  end;
  ctvKompl.Items.EndUpdate;   ctvKompl.Update;

       Второй запрос получает детали, которые являются детьми текущего узла (входят в группу деталей) и, которые существуют в таблице NSKompleks для данной модели АМТС. Это необходимо, потому что не для всех деталей, входящих в каталог АМТС, есть нормативы комплексов. Узлы для деталей строяться на том же уровне, что и для первого запроса.

  end; //  If tn=1 then
  If tn=0 then Begin

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

  qrTVKompl.Close;    qrTVKompl.SQL.Clear;
  qrTVKompl.SQL.Add('Select distinct g.GoodsNo,g.'+sGdCode
                   +',g.GdNames,k.NSKmplTime from Goods g, NSKompleks k');
  qrTVKompl.SQL.Add(' where g.GoodsNo=k.NSCarKompl '
                   +'and k.NSKmplTime is not null');
  qrTVKompl.SQL.Add(' and k.NSCarModel='+IntToStr(iGrpModel)
                   +' and k.NSCarDetal='+IntToStr(y));
  qrTVKompl.SQL.Add(' order by g.'+sGdCode);
  qrTVKompl.Open;  qrTVKompl.First;

       Запрос выбирает комплексы из таблицы NSKompleks, которые соответствуют выбранной модели АМТС и детали. Так как комплекс состоит из включенных и дополнительных операций, то выбираются только те записи, у которых норматив комплекса имеет значение, т.е. заголовок комплекса.

  Node.DeleteChildren;    ctvKompl.Items.BeginUpdate;
  While not qrTVKompl.Eof do Begin
     sCod := qrTVKompl.FieldValues[sGdCode];
     sFld := qrTVKompl.FieldValues['GdNames'];
     z    := qrTVKompl.FieldValues['GoodsNo'];
     If not VarIsNull(qrTVKompl.FieldValues['NSKmplTime'])
        then rFld := qrTVKompl.FieldValues['NSKmplTime'];
     sFld := sCod+' ['+FloatToStrF(rFld,ffFixed,5,1)+'] '+sFld;
     KomplNode := ctvKompl.Items.AddChild(Node,sFld);
     KomplNode.ImageIndex := z;
     KomplNode.OverlayIndex := -1;
     qrTVKompl.Next;
  end;

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

  ctvKompl.Items.EndUpdate;   ctvKompl.Update;
  end;  // If tn=0 then
end;

       И, наконец, для того чтобы визуально разделять узлы дерева на группы деталей, детали и комплексы, узлы раскрашиваются следующей процедурой:

procedure TForm1.ctvKomplCustomDrawItem(Sender: TCustomTreeView; 
                           Node: TTreeNode; State: TCustomDrawState;
                           var DefaultDraw: Boolean);
var
  NodeRect: TRect;
begin
  with ctvKompl.Canvas do
  begin
    if cdsSelected in State then
    begin
      Brush.Color := clInfoBk;
      NodeRect := Node.DisplayRect(True);
      ctvKompl.Canvas.FillRect(NodeRect);
    end;
    If Node.OverlayIndex=1 then  ctvKompl.Canvas.Font.Color := clMaroon;
    If Node.OverlayIndex=0 then  ctvKompl.Canvas.Font.Color := clGreen;
    If Node.OverlayIndex=-1 then ctvKompl.Canvas.Font.Color := clBlue;
  end;
end;

       Процедура CustomDrawItem вызывается при рисовании узла. В зависимости от признака в Node.OverlayIndex шрифту присваивается соответствующий цвет.

 

Выбор комплекса как ремонтного воздействия

       Несмотря на то, что в Delphi 7 нет CheckTreeView, выборку комплекса все равно можно осуществлять, например, с помощью всплывающего меню. Основная проблема будет в том, как отобразить на дереве выбранные комплексы. Это можно сделать, например, с помощью Node.OverlayIndex=2. Это мало скажется на построении дерева.
       Для хранения состояния выбранных комплексов используется таблица

Create table NSTreeViewCheck (
       NSNodeParnt   Integer not null references Goods on update cascade
                                                       on delete cascade,
       NSNodeCheck   Integer not null references Goods on update cascade
                                                       on delete cascade,
       NSCheckStat   Integer,
Primary Key (NSNodeParnt,NSNodeCheck));

       Константы состояния в RzCheckTreeView имеют следующие значения:
       csUnknown = 0 — неизвестное состояние,
       csUnchecked = 1 — невыбранный,
       csChecked=2 — выбранный,
       csPartiallyChecked=3 — частично выбранный.

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

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




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


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