//АЦК: Выгрузка организаций
//Постановка: Exchange #1562
uses
//  'Un_Report',
  'Un_Exch2719',    // exchange #2719
  'Un_Export',
  'Un_Default',
  'QueryWork',
  'QueryKbk',
  'Un_SysParams',     // exchange #2412
  'QueryTools',
  'Un_MessageValidForm',
  'Un_VersionSupport',
  'Un_Schems',
  'Un_ToolsXml',
  'Un_Tools',
  'Un_Const',
  'Un_Progress',
  'Un_System',
  'Un_Log',
  'Un_Debug';

const
  TestFileName = 'Exp_Fk_PU.Xml';
  
var
  Form: TForm;
  PacketNumber: Integer;
  DolIsp, FioIsp, TelIsp, KodRegion, OrgRoles, param_Num_Per, param_TypeIzm, IniSection: String;
  par_OpenOrg, par_NotCloseOrg, OutListOrg, param_OrgWithAcc, param_Org,
  param_ChangeOrg, param_ActualOrgByBudget, EmptySysParam: Boolean;
  //объекты визуальной форма для фильтра организаций
  //SysParam,
  OldOrg, NodeXmlList, BudgXml, Fo, Ufk, Node: Variant;
  FmOrg: TFormCustomList;
  CdsData: TClientDataSet;
  EdtIdOrg: TEdit;
  Budget: String;
  j, BudgIndx: Integer;
  vCfg_OrgRolesExclude: string;   // exchange #2534
  vFilterChangesOrgID: string;   // exchange #2632
  vFilterChangesOrgIDParams: TStringList;   // exchange #2632
  vFilterChangesOrgIDCount: integer;
  vFilterMode: integer;
  vCurrentBudget: string;


{~SearchNodeIntoXmlTreeAV
  рекурсивный обход узлов древовидного Xml, начиная с aXmlRootNode,
  для поиска узла с атрибутом aAttrName со значением aAttrValue.
  каждый узел должен содержать два атрибута: ID и PARENT_ID.
  результат:
    узел или NULL}
function SearchNodeIntoXmlTreeAV( aXmlRootNode: variant;
                                  aAttrName, aAttrValue: string): Variant;

  {~DoSearchNode_
    рекурсивный обход узлов, начиная с aRootNode, для поиска узла с
    атрибутом "ID" равным aParentID.
    результат:
      узел или NULL}
  function DoSearchNode_( aRootNode: Variant): Variant;
  var
    i: integer;
    xNode: Variant;
  begin

      try
        for i := 0 to aRootNode.childNodes.Length - 1 do begin
          xNode := aRootNode.childNodes.Item[ i];
          if ( xNode.GetAttribute( aAttrName) = aAttrValue) then begin
            Result := xNode;
            EXIT;
          end;
          Result := DoSearchNode_( xNode);
          if NOT VarIsNULL( Result)
            then EXIT;
        end;//for//
        Result := NULL;
        except
          ExceptionProc( 'DoSearchNode_');
      end;

  end;

begin

    try
      Result := DoSearchNode_( aXmlRootNode);
      except
        ExceptionProc( 'SearchNodeIntoXmlTreeAV');
    end;

end;


{~XmlListToXmlTree
  алгоритм формирования дерева.
  суть такова:
  в начале в корневой узел добавляются те записи, ссылки parentid у которых
  отсутствуют в наборе данных.
  затем усуществляется добавление всех прочих записей с поиском parentid.
  выполняется столько проходов, пока не будет добавлен ни один узел на
  дерево.
  особенность такого метода допускает хоть какое то отображение progressbarа.
  для ускорения формирования дерева желательно упорядовачивать набор данных
  относительно вложенных элементов (т.е. линейный список элементов, где
  потомки располагаются за записями родителей).
  каждый узел должен содержать два атрибута: ID и PARENT_ID.}
procedure XmlListToXmlTree( aFromNode, aTreeNode: Variant);
var
  xRootNode: Variant;
  i,j: integer;
//  xListXml: Variant;
  xXmlNode: Variant;
  xParentID: string;
  xFound: boolean;
  xFoundNode: Variant;
begin

    try
//      xListXml := aFromNode; //aListXml.OwnerDocument.DocumentElement;
      xRootNode := aTreeNode;

      // в начале определяем список записей, у которых parentid ссылается
      // на корневой элемент или вообще ни на какие id из списка записей.
      // в этом случае, такие id будут размещатся в корневых узлах
      for i := 0 to aFromNode.childNodes.Length - 1 do begin
        xXmlNode := aFromNode.childNodes.Item[ i];
        xParentID := xXmlNode.GetAttribute( 'PARENT_ID');

        xFound := FALSE;
        for j := 0 to aFromNode.childNodes.Length - 1 do
          if (aFromNode.childNodes.Item[ j].GetAttribute( 'ID') = xParentID)
            then begin
              xFound := TRUE;
              BREAK;
            end;

        if NOT xFound then begin
          // помечаем узел, что он уже проверен
          xXmlNode.SetAttribute( '_Z_', '1');
          xRootNode.AppendChild( xXmlNode.CloneNode( FALSE));
        end;
      end;

      // если корневых элементов не добавлено, значит дерево создать нельзя
      if (xRootNode.childNodes.Length <= 0) then begin
        WriteInfo( 'Операция структурирования списка невозможна.');
        EXIT;
      end;

      // теперь выполняем столько проходов по набору данных, пока за
      // очереднойй проход не будет добвалено ни одного элемента на
      // дерево. при этом заранее определить число проходов можно, но
      // вряд-ли это ускорит общую операцю.
      // для ускорения используется временный массив A в котором значение
      // TRUE указывает что запись еще не повешена на дерево.
      // в результате повторных проходов число проверенных записей
      // уменьшается, что значительно ускоряет процесс формирования
      // дерева.
      repeat
        xFound := FALSE;
        for i := 0 to aFromNode.childNodes.Length - 1 do begin
          xXmlNode := aFromNode.childNodes.Item[ i];
          // проверенные узлы игнорируем
          if (GetNodeAttributeEx( xXmlNode, '_Z_') = '') then begin
            xFoundNode := SearchNodeIntoXmlTreeAV(
                                         xRootNode,
                                         'ID',
                                         xXmlNode.GetAttribute( 'PARENT_ID'));
            if NOT VarIsNULL( xFoundNode) then begin
              xFound := TRUE;
              // помещаем элемент как дочерний к найденному узлу
              xFoundNode.AppendChild( xXmlNode.CloneNode( FALSE));
              // помечаем узел как проверенный
              xXmlNode.SetAttribute( '_Z_', '1');
            end;
          end;
        end;//for//
        // если ни одного узла не прикреплено к дереву - завершаем итерации
        if NOT xFound then BREAK;
      until FALSE;
      except
        ExceptionProc( 'XmlListToXmlTree');
    end;

end;

{~XmlTreeToXmlList
  формирование списка узлов в дочерних узлах aToNode
  из списка древовидных узлов aXmlRootNode.
  текущий узел aXmlRootNode в линейный список помещается,
  если параметр aWithRootNode = true.
  каждый узел должен содержать два атрибута: ID и PARENT_ID.}
procedure XmlTreeToXmlList( aXmlRootNode, aToNode: Variant;
                            aWithRootNode: boolean = FALSE);

  {~Do_
    рекурсивный обход узлов с формированием результата в списочный xml}
  procedure Do_( aRootNode: Variant);
  var
    i: integer;
    xNode: Variant;
  begin

      try
        for i := 0 to aRootNode.childNodes.Length - 1 do begin
          xNode := aRootNode.childNodes.Item[ i];
          aToNode.AppendChild( xNode.CloneNode( FALSE));
          // теперь обходим дочерние узлы рекурсивно
          Do_( xNode);
        end;//for//
        except
          ExceptionProc( 'Do_');
      end;

  end;

begin

    try
      // помещаем текущий узел в результат, если это указано
      if aWithRootNode
        then aToNode.AppendChild( aXmlRootNode.CloneNode( FALSE));
      // помещаем все дочерние элементы
      Do_( aXmlRootNode);
      except
        ExceptionProc( 'XmlTreeToXmlList');
    end;

end;


procedure Main(Xml: Variant);
var
  BudgetXml: Variant;
  xOldOrgDoc: Variant;
  xClearOldDownload: boolean;
  xBreak: boolean;
  xXmlNode: Variant;
  xTreeXmlBudget: Variant;
  xTreeNode: Variant;
begin
  // exchange #2632
  vFilterChangesOrgIDParams:= NIL;

  xml := InitializeMain( xml, TestFileName);
  if VarIsNull( xml) then exit;
  try
    try
      // если инициализация не выполнена - скрипт не должен выполняться
      if NOT Initialize(Xml) then EXIT;

      // exchange #2632
      vFilterChangesOrgIDParams:= TStringList.Create;

//      SysParam := CreateOleObject('Microsoft.XmlDom');
      LoadDataInSysParam;

      // exchange #2719
      // если дата выгрузки во входной xml не указана, то принимаем
      // дату выгрузки рабочей датой при логине.
      // это связано с тем, что скриптам, вызывающимся из меню, а не из формы,
      // атрибут EXCH_DATE в xml (в параметре вызова функции скрипта)
      // может не передаваться из эксча.
      if (LENGTH( VarToStr( Xml.documentElement.getAttribute('EXCH_DATE'))) <= 0)
        then ExportOutDirExch2719( DateTimeToXml( Work_Date), 'PU')
        else ExportOutDirExch2719(
                      VarToStr( Xml.documentElement.getAttribute('EXCH_DATE')),
                      'PU');

      (*
      EmptySysParam := QueryField('Select Id From SysParam Where Name = ?',
        ['exchange.orgcompare'], 'ID') = '';
      with SysParam.appendChild(SysParam.createElement('EXCH_UTILS_PROCESSOR')) do
      begin
        setAttribute('action', 'get_sys_param_value');
        if EmptySysParam then
          setAttribute('NAME', 'exchange.orgcompare')
        else
        with appendChild(SysParam.createElement('SYSPARAM')) do
          setAttribute('NAME', 'exchange.orgcompare');
        SysParam.loadXml(ExecXml(SysParam.xml));
        SysParam := SysParam.getElementsByTagName('PARAM_VALUE');
        if SysParam.length = 1 then
          OldOrg.loadXml(SysParam.items[0].firstChild.Data);
      end;
      *)
      {
      // если "Выгружать только измененные=да",
      // тогда параметр "Выгружать открытые организации" игнорируем
      // (принимаем как установленный =нет)
      if param_ChangeOrg
        then par_OpenOrg := FALSE;
        }
{
      // exchange #2632
      // если выставлены параметры:
      // выгружать открытые орг = нет
      // И выгружать закрытые орг = нет
      // И Выгружать только измененные организации = нет
      // то выгружать по сути нечего, поэтому сразу ругаемся и выходим
      if ((NOT par_OpenOrg) AND par_NotCloseOrg AND (NOT param_ChangeOrg))
        then begin
          ShowMessage( 'По указанным настройкам выгружать нечего.');
          EXIT;
        end;
}
      //если не надо выгружать изменения организаций, то отчищаем
      //загруженный сиспарам, также если параметр в базе не найден, то создаем xml
      //с нуля
      xClearOldDownload := FALSE;
      if EmptySysParam
        then xClearOldDownload := TRUE
        else
          // если "Выгружать только измененные=нет"
          if (not param_ChangeOrg) then
            // если "Выгружать закрытые организации=да"
            // И "Выгружать открытые организации=нет" то старую выгрузку
            // не чистим
            if ((NOT par_OpenOrg) AND (NOT par_NotCloseOrg))
              then xClearOldDownload := FALSE
              else xClearOldDownload := TRUE;


      if xClearOldDownload then begin
        OldOrg := CreateOleObject('Microsoft.XmlDom');
        OldOrg.appendChild(OldOrg.createElement('EXCHQUERY'));
        OldOrg.documentElement.setAttribute('completed', 'true');
      end;

      xOldOrgDoc := OldOrg.documentElement;
      OldOrg := xOldOrgDoc;
      WriteLog('Получение списка бюджетов', True);

      // exchange #2969
      // список бюджетов за прилогиненный год. нужна не вся инфа, а только
      // поля ID,PARENT_ID,CAPTION,FINYEAR.
      // это нужно чтобы получить древовидный список бюджетов за текущий год.
      // из него потом получим текущий бюджет со всеми вложенными.
      if param_Org or param_ActualOrgByBudget then begin

        BudgXml := QueryXml( ' Select b.ID,b.PARENT_ID,b.CAPTION,b.FINYEAR'
                            +' From Budget b where b.FINYEAR=?',
                              [ FormatDateTime( 'yyyy', Work_Date)]);
        BudgXml := BudgXml.documentElement;
        // создаем структуру бюджетов
        // это нужно чтобы получить список вложенных бюджетов в прилогиненный
        xTreeXmlBudget := CreateOleObject('Microsoft.XmlDom');
        xTreeXmlBudget.AppendChild( xTreeXmlBudget.CreateElement('BUDGETS'));
        XmlListToXmlTree( BudgXml,
                          xTreeXmlBudget.DocumentElement);
        // выбираем только прилогиненный бюджет и вложенные в него
        xTreeNode := SearchNodeIntoXmlTreeAV(
                              xTreeXmlBudget.DocumentElement,
                              'ID',
                              Budget_Id);
        if VarIsNull( xTreeNode) then begin
          WriteInfo( 'Прилогиненный бюджет не найден в списке загруженных бюджетов.');
          EXIT;
        end;
        // формируем списковую структуру бюджетов из древовидной.
        // это нужно потому что перебор осуществляется из линейного списка.
        BudgXml.OwnerDocument.LoadXml( '');
        BudgXml := CreateOleObject('Microsoft.XmlDom');
        BudgXml.AppendChild( BudgXml.CreateElement('BUDGETS'));
        BudgXml := BudgXml.documentElement;
        // теперь помещаем в список все вложенные бюджеты
        // добавляем в список бюджетов прилогиненный бюджет
        XmlTreeToXmlList( xTreeNode, BudgXml, TRUE);

      end;



      BudgIndx := 0;
      repeat
        // exchange #2969
        // добавлено описание текущего бюджета в название диалога списка
        // организаций и журнал выгрузки.
        if param_Org or param_ActualOrgByBudget then begin
          xXmlNode := BudgXml.childNodes.Item[BudgIndx];
          Budget := xXmlNode.getAttribute('ID');
          vCurrentBudget := FORMAT( 'бюджету "%s" %s года',
                                    [ GetNodeAttributeEx( xXmlNode, 'CAPTION'),
                                      GetNodeAttributeEx( xXmlNode, 'FINYEAR')]);
          // делаем отступ в журнале для более понятного разбора
          WriteLog( '-------------------------------------------------------');
          WriteLog( FORMAT( 'Выгрузка организаций из бюджета %s...',
                            [ GetNodeAttributeEx( xXmlNode, 'CAPTION')]));
        end else begin
          Budget := Budget_Id;
          vCurrentBudget := 'прилогиненному бюджету';
          WriteLog( 'Выгрузка организаций из прилогиненного бюджета...');
        end;
        //ищем нужный бюджет в значении из sysparam
        NodeXmlList := OldOrg.SelectNodes('B' + Budget);
        if (NodeXmlList.Length = 0) then
          OldOrg := OldOrg.appendChild(OldOrg.ownerDocument.createElement('B' + Budget))
        else
          OldOrg := NodeXmlList.Item[0];
        Data := CreateOleObject('Microsoft.XmlDom');
        Data.appendChild(Data.createElement('FORMAT'));
        WriteLog('Определение данных Финансового органа', True);
        Fo := GetOrgByBudget(Budget);
        if Fo.documentElement.childNodes.Length <> 1 then
          Fo := QueryXml(sqlOrgByBudget, [Budget]);
        Fo := Fo.documentElement.childNodes.Item[0];;
        WriteLog('Определение данных ТОФК', True);
        Ufk := GetTofkByBuh(Budget);
        if Ufk.documentElement.childNodes.Length = 0 then
          Raise('Не удалось получить реквизиты организации ТОФК. ' +
          'Проверьте заполение счета в АЦК "Бухгалтерия-Настройка счетов-Счета ФО-' +
          'Основной бюджетный счет".');
        Ufk := Ufk.documentElement.childNodes.Item[0];
        IniSection := 'Перечень УБП';
        //заполнение содержимого файла.
        // exchange #2969
        // добавлен флаг прерывания выгрузки.
        xBreak := FALSE;
        if MakeFormat( xBreak) then
        begin
          //валидация схем.
          if param_CtlValidOn then
            if not ValidateFormat then
            begin
              ShellExecute('NotePad', FileValidate, False);
              WriteLog('Не пройден контроль валидности отдельными документами');
              if MessageDlg('Не пройден контроль валидности.' + cr +
                'Продолжить выгрузку?', mtConfirmation, mbYes + mbNo, 0) = mrNo then
              begin
                WriteLog('Выгрузка прервана пользователем');
                Exit;
              end;
            end;
          try
            FileOut := DirOut + FileOut;
            WriteLog('Сохранение в исходящем каталоге: ' + FileOut, True);
            XmlToTextFile(FileOut);
            //Запись номера посылки в течении дня
            with TIniFile.Create(DirIni + 'Schems\Send.dat') do
            begin
              WriteString('Перечень УБП', FormatDateTime('yyyymmdd', Now),
              IntToStr(PacketNumber));
              Free;
            end;
          except
            raise('Ошибка сохранения: ' + FileOut);
          end;
          //сохраняем данные по выгруженным организациям в Xml для sysparam
          OldOrg := xOldOrgDoc;//OldOrg.parentNode;
        end
        else begin
          // exchange #2969 - прерывание выгрузки
          if xBreak then BREAK;
          OldOrg := xOldOrgDoc;//OldOrg.parentNode;
          WriteLog('Ни одной организации не выгружено, поэтому файл не сформирован.');
        end;

        // exchange #2969
        // изменена проверка завершения перебора бюджетов чтобы код был понятнее
        if param_Org or param_ActualOrgByBudget then begin
          Inc(BudgIndx);
          if (BudgIndx >= BudgXml.childNodes.Length) then BREAK;
        end else begin
          BREAK;
        end;
      until FALSE;
      //сохраняем данные выгруженных организаций
      SaveDataInSysParam;
    except
      WriteLog(LastException.Message);
      raise(LastException.Message);
    end;
  finally
    // exchange #2632
    if (vFilterChangesOrgIDParams <> NIL)
      then vFilterChangesOrgIDParams.Free;

    Finalize(Xml);

    // exchange #2632
    // поскольку при выгрузке не все организации могут выгружаться
    // то сообщения выдаются в лог, который должен показываться при
    // завершении скрипта.
    ShowJournalEnd;
    {
    if FileExists(FileInfo)
      then ShellExecute('NotePad', FileInfo, False);
      }
  end;
end; {end Main}


{~Initialize
  инициализация скрипта.
  вместо raise выполнена переделка подпрограммы на функцию. это неверно,
  поскольку raise предназначены не для этого.
  вместо raise отображается сообщение в окне диалога с кнопкой OK и функция
  возвращает значение =FALSE.
  результат:
    =true - инициалицазия выполнена успешно;
    =false - обнаружена ошибка и скрипт выполнятся не должен.}
function Initialize(Xml: Variant): boolean;
begin

    try
      Result := FALSE;
      Form := NIL;

      with TStringList.Create do
      try
        CommaText := InitializeDefault(Xml, ChangeFileExt(TestFileName, ''));

        // инициализируем модуль сообщений
        InitializeMessageValid( 'Exp_Fk_PU', DirIni);

        //визиуализация выгрузки
        Form := TForm.Create(Application);
        if not FileExists(DirIni + 'Forms\Fm_Process.dfm') then begin
          ShowMessage('Не найден шаблон формы:' + DirIni + 'Forms\Fm_Process.dfm');
          EXIT;
        end;
        LoadForm(DirIni + 'Forms\Fm_Process.dfm', Form);
        vProgressBar := TProgressBar(Form.FindComponent('ProgressBar'));
        vStatusBar := TStatusBar(Form.FindComponent('StatusBar'));
        DolIsp := Values['Должность исполнителя'];
        FioIsp := Values['ФИО исполнителя'];
        TelIsp := Values['Телефон исполнителя'];
        KodRegion := Values['Код региона'];
        OrgRoles := Values['Роли организаций'];
        par_OpenOrg := Values['Выгружать открытые организации'] = 'Да';
        par_NotCloseOrg := Values['Выгружать закрытые организации'] = 'Нет';
        param_OrgWithAcc := Values['Выгрузка только УБП со счетом в ФК'] = 'Да';
        param_Org := Values['Выгружать УБП из'] = 'бюджетных строк';
        OutListOrg := Values['Выгрузка организаций из списка'] = 'Да';
        param_Num_Per := Values['Номер перечня'];
        param_ChangeOrg := Values['Выгружать только измененные организации'] = 'Да';
        param_TypeIzm := Values['Тип изменения'];
        if (Trim(DolIsp) = '') or (Trim(FioIsp) = '') or (Trim(TelIsp) = '') or
          (Trim(param_Num_Per) = '') then begin
          ShowMessage('Не заполнены обязательные параметры. Проверьте заполнение' +
            ' следующих настроек: "Должность исполнителя", "ФИО исполнителя",' +
            ' "Телефон исполнителя", "Номер перечня"');
          EXIT;
        end;
        //Чтения номера посылки в течении дня
        with TIniFile.Create(DirIni + 'Schems\Send.dat') do
        begin
          PacketNumber := StrToInt(ReadString('Перечень УБП',
            FormatDateTime('yyyymmdd', Now), '0')) + 1;
          Free;
        end;
        if (OutListOrg) and (not FileExists(DirIni + 'Forms\FmOrg.dfm'))
          then begin
            ShowMessage('Не найден шаблон формы:' + DirIni + 'Forms\FmOrg.dfm');
            EXIT;
          end;

        // exchange #2399 пп.29 ТФФ 10.0
        // exchange #2542 - переход на тфф11 + переработка модуля проверки версий схем
        LoadSchema( GetFileNameSchemaTFF( 'pu_txpu*'));
        {
        LoadSchema(
            DirIni + VersionSchemsDir
              + IfVersionSupportThenElse(
                  cnVersionTFF_10,
                  'pu_txpu130101.xml',
                  'pu_txpu100701.xml'));
         }
        param_ActualOrgByBudget := Values['Выгружать только актуальные организации по бюджетам'] = 'Да';

        // exchange #2534
        vCfg_OrgRolesExclude:= TRIM( values[ 'Кроме ролей организаций']);

        Result := TRUE;
      finally
        Free;
      end;
      except
        ExceptionLog( 'Сбой Initialize');
    end;

end; {End Initialize}




{~Fill_PUST
  добавление раздела PUST с заполнением значений из узла aNode.

  поле PUST.TYPE_IZM заполняется значением aTypeNum и aTypeStr.

  поле PUST.STR_IZM заполняется из параметра aNumStr, если строка aNumStr
      указывается не пустой.

  изменение поля PUST.TYPE_IZM (тип изменения)
  или по старому - PUST.VID_IZM.

  aTypeNum - строка.
              допустимое значение: 1 или 2 или 3 или 4.

  aTypeStr - строка для старого заполнения PUST.VID_IZM.
              этот параметр можно не указывать.

  это нужно для того, чтобы избежать дублирования кода, в случае
  доработки изменения операции или изменении заполнения раздела PUST по ТФФ.}
procedure Fill_PUST( aNode: Variant;
                          var aData: Variant;
                          aKodRegion: string;
                          aBudgetID: string;
                          aTypeNum: string;
//                          aTypeStr: string = '';
                          aNumStr: string = '');
var
  xData: Variant;
begin

    try
      //старые данные организации - изменяемая строка (TYPE_IZM = 3).
      xData := aData.appendChild( FillElementBySchema( aNode, 'PUST'));

      xData.setAttribute('KOD_RG', aKodRegion);

      if ( aNode.getAttribute('KADM_CODE') <> '')
        then xData.setAttribute( 'KOD_PPP',
                                 KadmCodeFormat( aNode.getAttribute('KADM_CODE'),
                                                 aBudgetID));

      if ( LENGTH( aNumStr) > 0)
        then xData.setAttribute('STR_IZM', aNumStr);


      //exchange #2413 - удален атрибут из PUST
      //Data.setAttribute('VID_IZM', aTypeStr);
      xData.setAttribute('TYPE_IZM', aTypeNum);


      except
        ExceptionLog( 'Сбой Fill_PUST');
    end;

end;


{~UpdateOldOrg
  обновление описания организации из aXmlOrgNew в списке организаций
  системного параметра aOldOrg.
  прежнее описание организации находится в узле aNodeOrgOld.

  подпрограмма удаляет узел aNodeOrgOld, и добавляем в aOldOrg клон
  узла aXmlOrgNew.

  если aNodeOrgOld указан как NULL, то описание организации aXmlOrgNew
  добавляется в список aOldOrg.

  это нужно для того, чтобы избежать дублирования кода, в случае
  доработки изменения операции.}
procedure UpdateOldOrg( aOldOrg, aNodeOrgOld, aXmlOrgNew: Variant);
begin

    try
      //замена старых данных новыми (для сохранения в сис парам.)
      if NOT VarIsNull(aNodeOrgOld)
        then aNodeOrgOld.parentNode.RemoveChild( aNodeOrgOld);
      aOldOrg.appendChild( aXmlOrgNew.cloneNode( False));

      except
        ExceptionLog( 'Сбой UpdateOldOrg');
    end;

end;


{~IsChanges
  проверяем, изменилось ли описание организации aNewNode относительно
  предыдущей выгрузки aOldNode.
  результат:
    =true - изменение обнаружены.

  exchange #2632
  добавлена проверка изменения даты закрытия.
  это значит что могут выгрузить орг. как измененную (ранее закрытую,
  а по новому - открытую).

  exchange #3052
  убрана проверка поля версии.
  в АЦК-Ф любое изменение поля вызывает изменение версии, поэтому даже
  если поменялось поле, которое здесь не проверяется, вызывает выгрузку
  "якобы" измененной организации.}
function IsChanges( aOldNode, aNewNode: Variant): boolean;
begin

    try
      if (  { (aOldNode.getAttribute('VERSION') <> aNewNode.getAttribute('VERSION'))
          or }(aOldNode.getAttribute('CAPTION') <> aNewNode.getAttribute('CAPTION'))
          or (aOldNode.getAttribute('DESCRIPTION') <> aNewNode.getAttribute('DESCRIPTION'))
          or (aOldNode.getAttribute('KADM_CODE') <> aNewNode.getAttribute('KADM_CODE'))
          or (aOldNode.getAttribute('ROOT_DESCRIPTION')
                                   <> aNewNode.getAttribute('ROOT_DESCRIPTION'))
          or (aOldNode.getAttribute('CLOSE_DATE') <> aNewNode.getAttribute('CLOSE_DATE')))
        then Result := TRUE
        else Result := FALSE;

      except
        ExceptionLog( 'Сбой IsChanges');
    end;

end;

procedure AddFiltedChangesID( aID: string);
begin

    try
      // если не указано значение для сохранения, значит это завершение
      // формирования фильтра
      if (LENGTH( aID) <= 0) then begin
        // нужно проверить, если в фильтре чтонибудь сохранялось
        // до дописываем завершение фильтра - закрывающую скобку.
        if (LENGTH( vFilterChangesOrgID) > 0)
          then vFilterChangesOrgID := vFilterChangesOrgID + ')';
        EXIT;
      end;

      // если список фильтра пустой, значит начинаем новое формирование фильтра
      if (vFilterChangesOrgIDParams.Count <= 0) then begin
        // поскольку список ид. можеть большим, на всякий случай формируем
        // перечень подусловий: and (id in (...) or id in (...)) вместо
        // одного большого: and id in (...).
        vFilterChangesOrgIDCount := 1;
        vFilterChangesOrgID := 'O.ID in (';
      end;

      if (vFilterChangesOrgIDCount >= 300) then begin
        vFilterChangesOrgIDCount := 1;
        vFilterChangesOrgID := vFilterChangesOrgID + ') or O.ID in (';
      end;

      if (vFilterChangesOrgIDCount > 1)
        then vFilterChangesOrgID := vFilterChangesOrgID + ',';

      vFilterChangesOrgID := vFilterChangesOrgID + '?';
      vFilterChangesOrgIDParams.Add( aID);
      INC( vFilterChangesOrgIDCount);
      except
        ExceptionLog( 'Сбой AddFiltedChangesID');
    end;

end;


{~DoUpdateLoadedOrg
  Exchange#2182}
procedure DoUpdateLoadedOrg( var aNode: Variant);
begin

    try
      if ( aNode.getAttribute('CAPTION_EMPTY') = '1')
        then aNode.setAttribute('CAPTION', aNode.getAttribute('DESCRIPTION'));

//      Result := aNode;
      except
        ExceptionLog( 'Сбой DoUpdateLoadedOrg');
    end;

end;


function MakeFormat( var aBreak: boolean): Boolean;
var
  Xml, Resperson: Variant;
  DateNow: TDate;
  i, numstr: Integer;
  xXmlNode: Variant;
  xDBGrid: TDBGridEh;
  s: string;
  n: integer;
  xID: string;
  xFilterID: string;          // exchange #2632
  xFilterIDParams: Variant; // exchange #2632
//  xTypeIzm: string;
//  xSQL: string;

begin

    try
      Result := False;

      Form.Caption := 'Выгрузка перечня ГРБС, РБС, ПБС, АИФДБ, АДБ...';
      Data := CreateOleObject('Microsoft.XmlDom');
      Data.appendChild(Data.createElement('FORMAT'));
      // Чтение номера посылки.
      with TIniFile.Create(DirIni + 'Schems\Send.dat') do
      begin
        PacketNumber := StrToInt(ReadString(IniSection,
          FormatDateTime('yyyymmdd', Now), '0')) + 1;
        Free;
      end;
      WriteLog('Формирование имени выгружаемого файла', True);
      FileOut := GetFileName(Fo.getAttribute('NAME'), 'PU', Now, PacketNumber,
        FileOutIsLocalNet, FileOutOrgCodeIsFk);
      Data := Data.documentElement;
      Form.Show;
      StatusBar.SimpleText := 'Заполнение данных файла: ' + FileOut;
      Data := Data.appendChild(FillElementBySchema(Fo, 'FK'));
      Data.setAttribute('FORM_VER', AppVersion);
      Data := Data.parentNode;
      Data := Data.appendChild(FillElementBySchema(Fo, 'FROM'));
      Data.setAttribute('BUDG_LEVEL', GetBudgLevel(Budget));
      Data := Data.parentNode;
      Data.appendChild(FillElementBySchema(Ufk, 'TO'));
      Resperson := RespersonFromSys('2', Budget);
      Xml := QueryXml(sqlBudgOrgOtv, [Budget]);
      Xml := Xml.documentElement.childNodes.Item[0];
      Data := Data.appendChild(FillElementBySchema(Xml, 'PU'));
      Data.setAttribute('DATE_FORM', FormatDateTime('dd.mm.yyyy', Now));
      Data.setAttribute('DATE_OTCH', FormatDateTime('dd.mm.yyyy', Now));
      Data.setAttribute('NUM_PER', param_Num_Per);
      Data.setAttribute('KOD_TOFK', Ufk.getAttribute('NAME'));
      Data.setAttribute('NAME_TOFK', Ufk.getAttribute('DESCRIPTION'));

      // exchange #2399 ТФФ10.0 пп.29
      // выгружать ОКПО из карточки организации, выгружаемой в поле PU.NAME_FO
      // exchange #2418 - OKPO должно выгружаться в поле OKPO_FO, а не NAME_FO.
      // exchange #2542 - переход на тфф11: проверка тфф10 лишняя
      { if IsVersionSupport( cnVersionTFF_10)
        then }Data.setAttribute( 'OKPO_FO', Xml.getAttribute('OKPO'));

      if not VarIsNull(Resperson.documentElement.getAttribute('HEAD_POST')) then
        Data.setAttribute('DOL_RUK_FO',
          Resperson.documentElement.getAttribute('HEAD_POST'));
      if not VarIsNull(Resperson.documentElement.getAttribute('HEAD_FIO')) then
        Data.setAttribute('NAME_RUK_FO',
          Resperson.documentElement.getAttribute('HEAD_FIO'));
      Data.setAttribute('DOL_ISP_FO', DolIsp);
      Data.setAttribute('NAME_ISP_FO', FioIsp);
      Data.setAttribute('TEL_ISP_FO', TelIsp);
      Data := Data.parentNode;



      // exchange #2632 (см. таблицу в задании)
      // определяем режим выгрузки по конфигу
      if (NOT param_ChangeOrg) then begin
        if (par_OpenOrg AND par_NotCloseOrg AND (NOT OutListOrg))
          then vFilterMode := 1
          else
        if (par_OpenOrg AND (NOT par_NotCloseOrg) AND (NOT OutListOrg))
          then vFilterMode := 2
          else
        if ((NOT par_OpenOrg) AND (NOT par_NotCloseOrg) AND (NOT OutListOrg))
          then vFilterMode := 3
          else
        if (par_OpenOrg AND par_NotCloseOrg AND OutListOrg)
          then vFilterMode := 4
          else
        if (par_OpenOrg AND (NOT par_NotCloseOrg) AND OutListOrg)
          then vFilterMode := 5
          else
        // exchange #2640
        // когда "выгружать открытые=нет" и "выгружать закрытые=нет"
        // тогда выгружаются все орг. не влияя на флаг измененных
        // при этом не смотрим на дату открытия,
        // а дата закрытия должна быть null или >тек.даты
        if ((NOT par_OpenOrg) AND par_NotCloseOrg)
          then vFilterMode := 1
          else vFilterMode := 6;
      end else begin
        if (par_NotCloseOrg AND (NOT OutListOrg))
          then vFilterMode := 7
          else
        if ((NOT par_NotCloseOrg) AND (NOT OutListOrg))
          then vFilterMode := 8
          else
        if (par_NotCloseOrg AND OutListOrg)
          then vFilterMode := 9
          else vFilterMode := 10;
      end;


      // exchange #2632
      // для случая (выгружать из измененных) добавлен подзапрос:
      // только те орг. которые входят в список выгруженных.
      // причем, чтобы формировать параметрический запрос, формируем отдельно
      // строку подзапроса и отдельно список параметров.
      vFilterChangesOrgID := '';
      vFilterChangesOrgIDParams.Clear;
      {
      if (OldOrg.ChildNodes.Length <= 0) then begin
        if NOT param_ChangeOrg then begin
          // если раньше ничего не выгружалось и стоит "выгружать измененные=да",
          // тогда ничего в списке показывать не будем (выгружать-то нечего).
          vFilterChangesOrgID := 'O.ID = ?';
          vFilterChangesOrgIDParams.Add( '-1');
        end;
      end else begin
      }
      if param_ChangeOrg then begin
        // в начале выбираем все орг.
        Xml := GetOrgRefEx( par_OpenOrg,
                            par_NotCloseOrg,
                            param_OrgWithAcc,
                            param_Org,
                            OrgRoles,
                            Budget,
                            FALSE,
                            vCfg_OrgRolesExclude);
        Xml := Xml.documentElement;
        // теперь фильтруем только измененные
        for i := 0 to Xml.ChildNodes.Length - 1 do begin
          xXmlNode := Xml.ChildNodes.items[i];

          DoUpdateLoadedOrg( xXmlNode);

          xID := xXmlNode.getAttribute('ID');
          //поиск организации в сис.парам.
          Node := OldOrg.selectNodes('./ROW[@ID=' + xID + ']');
          if (Node.length <= 0)
            // если орг. ранее не выгружалась - добавляем в фильтр
            then AddFiltedChangesID( xID)
            else begin
              //найдена организация, выгружавшаяся ранее, проверяем изменение/
              // если изменена - добавляем в фильтр
              Node := Node.item[0];
              if IsChanges( Node, xXmlNode)
                then AddFiltedChangesID( xID);
          end;
        end;//for//
        // завершения формирования списка - сохранения пустого ID
        AddFiltedChangesID( '');

//        if (OldOrg.ChildNodes.Length <= 0)
        if (vFilterChangesOrgIDParams.Count <= 0) then begin
          // если нет разницы, тогда ничего в списке показывать не будем
          vFilterChangesOrgID := 'O.ID = ?';
          vFilterChangesOrgIDParams.Add( '-1');
        end;
(*
        if param_ChangeOrg then begin
          if (vFilterChangesOrgIDParams.Count <= 0) then begin
            vFilterChangesOrgID := 'O.ID = ?';
            vFilterChangesOrgIDParams.Add( '-1');
          end;
        end else begin
          if (vFilterChangesOrgIDParams.Count > 0) then begin
            vFilterChangesOrgID := ' NOT (' + vFilterChangesOrgID + ')';
          end;
        end;
        (*

          if (n >= 300) then begin
            n := 1;
            vFilterChangesOrgID := vFilterChangesOrgID + ') or O.ID in (';
          end;

          if (n > 1)
            then vFilterChangesOrgID := vFilterChangesOrgID + ',';

          vFilterChangesOrgID := vFilterChangesOrgID + '?';
          vFilterChangesOrgIDParams.Add( xID);
          INC( n);
        end;
        if (LENGTH( vFilterChangesOrgID) > 0)
          then vFilterChangesOrgID := vFilterChangesOrgID + ')';
    *)
      end;


      //запрос к базе по организациям.
      if OutListOrg then begin
        FmOrg := TFormCustomList.Create(Application);
        FmOrg.LoadDfm(DirIni + 'Forms\FmOrg.dfm');
        // exchange #2969
        // в название окна добавлено описание бюджета.
        // это сделано если выполняется перебор из списка бюджетов
        FmOrg.Caption := FORMAT( 'Список организаций к выгрузке по %s ...',
                                  [ vCurrentBudget]);
        FmOrg.OnBeforRefresh := @OnBeforRefresh;
        CdsData := TClientDataSet(FmOrg.FindComponent('CdsData'));
        EdtIdOrg := TEdit.Create(FmOrg);
        EdtIdOrg.Text := '';
        FmOrg.Edit := EdtIdOrg;
        try
          FmOrg.SQLText := 'принудительный рефреш';
          case FmOrg.ShowModal of
            // exchange #2969
            // добавлена кнопка прерывания выгрузки.
            // при ее нажатии закрывается диалог с прерыванием цикла выгрузки.
            mrAbort: begin
                       WriteLog('Прерывание выгрузки пользователем.');
                       aBreak := TRUE;
                       EXIT;
                     end;
          end;
    //      if EdtIdOrg.Text = '' then begin

          xDBGrid := TDBGridEh( FmOrg.FindComponent('DBGrid'));
          if (xDBGrid.Bookmark.Count <= 0) then begin
            ShowMessage('Ни одной организации не выбрано.');
            WriteLog('Ни одной организации не выбрано.');
            Exit;
          end;

          // exchange #2632
          // формируем список id выбранных организаций с учетом параметризации
          // запроса.
          xFilterID       := 'O.ID in (';
          xFilterIDParams := NULL;
          n := 1;
          for i:= 0 to xDBGrid.Bookmark.Count-1 do begin
            if (n >= 300) then begin
              n := 1;
              xFilterID := xFilterID + ') or O.ID in (';
            end;

            if (n > 1)
              then xFilterID := xFilterID + ',';

            xFilterID := xFilterID + '?';
            AddToVarArray( xFilterIDParams, xDBGrid.Bookmark[i]);
            INC( n);
          end;//for//
          if (LENGTH( xFilterID) > 0)
            then xFilterID := xFilterID + ')';

          Xml := QueryXml( sqlOrgRefActual
                            + ' and (' + xFilterID + ')'
                            +' Group by O.Id, O.Caption, O.Description,'
                            +' O.Kadm_Code, O.Version, O.Close_Date, '
                            +' O.Open_Date, ParentOrg.Name, ParentOrg.Caption,'
                            +' ParentOrg.Description,'
                            +' O.Taxcode, O.Kpp, O.Name, O.Caption_Empty',
                           xFilterIDParams);
        finally
          FmOrg.Free;
        end;
      end else
        try
          // exchange #2534
          // этот запрос списка организаций вызывается если выбор организаций
          // выполняется без диалога.
          Xml := GetOrgRefEx( par_OpenOrg,
                              par_NotCloseOrg,
                              param_OrgWithAcc,
                              param_Org,
                              OrgRoles,
                              Budget,
                              FALSE,
                              vCfg_OrgRolesExclude);
        except
          Raise( 'Некорректное значение параметра: "Роли организаций"'
                +#13#10 + LastException.Message);
        end;



      WriteLog('Заполнение данных файла: ' + FileOut);
      Xml := Xml.documentElement;
      ProgressBar.Max := Xml.childNodes.length;

      numstr := 0;
      for i := 0 to Xml.childNodes.length - 1 do begin
        ProgressBar.Position := i;
        xXmlNode := Xml.childNodes.items[i];

        DoUpdateLoadedOrg( xXmlNode);

    

        //поиск организации в сис.парам.
        Node := OldOrg.selectNodes('./ROW[@ID=' + xXmlNode.getAttribute('ID') + ']');
        if Node.length <> 0 then begin
          //найдена организация, выгружавшаяся ранее.
          //данные организации до изменения.
          Node := Node.item[0];



          // проверяем, изменилось ли описание организации относительно предыдущей
          // выгрузки.
          if NOT IsChanges( Node, xXmlNode) then
            // проверяем режим выгрузки
            // если выгружаются из списка - тогда выгружаем принудительно
            if NOT OutListOrg then begin

              CONTINUE;
            end;

          // exchange #2632
          // если обнаружено изменение даты закрытия (при этом остальные поля
          // могут изменится, а могут и нет) относительно предыдущей выгрузки
          if (Node.getAttribute('CLOSE_DATE') <> xXmlNode.getAttribute('CLOSE_DATE'))
            then begin
              // если обнаружено закрытие организации, т.е. в сист.параметре
              // для организации дата закрытия не указана, а в бд дата
              // закрытия указана
              if ( LENGTH( xXmlNode.getAttribute('CLOSE_DATE')) > 0) then begin
                // то выгружаем как "исключение" независимо от того, что указано
                // в параметре "Выгрузка организаций из списка" и "тип изменения"
                // выгружаем измененную строку с закрытием (тип 2)
    
                INC( numstr);
                Fill_PUST( xXmlNode, Data, KodRegion, Budget, '2');
                // обновляем узел описания организации в сист.параметре
                UpdateOldOrg( OldOrg, Node, xXmlNode);
                // переходим к след.организации
                CONTINUE;
              end else
                // если выгрузка выполняется не из списка, такую орг. не выгружаем.
                // а если выгружают из списка, дальше выполняется проверка
                // изменения полей (включая дату закрытия).
                if NOT OutListOrg then begin
                  // если организация ранее выгружалась как закрытая, а теперь
                  // обнаружили что она открытая, то пишем сообщение в лог,
                  // и такую орг.не выгружаем
                  WriteInfo( FORMAT( 'Организация "%s" (ID=%s) не выгружается. Дата закрытия изменена относительно последней выгрузки.',
                                    [ xXmlNode.getAttribute('CAPTION'),
                                      xXmlNode.getAttribute('ID')]));
                  // переходим к след.организации
                  CONTINUE;
                end;
            end else
              // если организация ранее выгружалась как закрытая, то проверяем,
              // если выгрузка выполняется не из списка, то такую организацию
              // выгружать нельзя. при этом в лог пишем сообщение.
              // но если выгрузка выполняется из списка, тогда выгружаем так,
              // как указано пользователем (измененная или с типом "тип выгрузки"
              // из конфига), если обнаружено изменение описания орг.
              if ( LENGTH( Node.getAttribute('CLOSE_DATE')) > 0) then begin
                if NOT OutListOrg then begin
                  WriteInfo( FORMAT( 'Организация "%s" (ID=%s) не выгружается. Уже была выгружена как закрытая.',
                                    [ xXmlNode.getAttribute('CAPTION'),
                                      xXmlNode.getAttribute('ID')]));
                end else begin
                  // выгружается закрытая орг.
                  inc(numstr);
                  Fill_PUST( xXmlNode, Data, KodRegion, Budget, '2');
                end;
                // переходим к след.организации
                CONTINUE;
              end;

          //организация изменялась.
          // если выгрузка выполняется из списка, и в параметре "тип изменения"
          // указан не пусто, то выгружаем новый раздел с указанным типом
          // из параметра "тип изменения" с пустым значением номера строки
          // в поле PUST.STR_IZM
          if (OutListOrg and (param_TypeIzm <> '')) then begin

            inc(numstr);
            Fill_PUST( xXmlNode, Data, KodRegion, Budget, param_TypeIzm);
          end else begin
            //дописываем данные организации после изменения согласно Exchange #1836
            //новые данные организации - изменяющая строка (TYPE_IZM = 4).


            // exchange #2632
            // если выгружается не из списка, или тип изменения в параметре не
            // указан, значит требуется выгрузить старую и новую организацию.
            // порядок должен быть таким: в начале с типом 3 (старая),
            // потом с типом 4 (новая).
            // Для строки, у которой «Код изменения» равен «4» (изменяющая),
            // значение поля «Номер строки» совпадает со значением поля
            // «Строка», соответствующей ему изменяемой строки.
            // Для строки, у которой «Код изменения» равен «3» (изменяемая)
            // значение поля «Номер строки» совпадает со значением поля
            // «Строка» соответствующей ему изменяющей строки.
            inc(numstr);
            Fill_PUST( Node, Data, KodRegion, Budget, '3', INTTOSTR( numstr + 1));
            inc(numstr);
            Fill_PUST( xXmlNode, Data, KodRegion, Budget, '4', INTTOSTR( numstr - 1));
          end;
          // обновляем узел описания организации в сист.параметре
          UpdateOldOrg( OldOrg, Node, xXmlNode);
        end else begin
        (*
          if param_ChangeOrg then begin
    
            CONTINUE;
          end;
          *)

          // если организация не найдена среди выгруженных
          // то проверяем: если выгружаем из списка и указан в конфиге
          // "тип выгрузки" = 2 (закрытие), то выгружаем с закрытием.
    
          inc(numstr);
          if (OutListOrg and (param_TypeIzm = '2'))
            then Fill_PUST( xXmlNode, Data, KodRegion, Budget, '2')
            else
              // проверяем, если у организации указана дата закрытия,
              // то тоже выгружаем с закрытием
              if ( LENGTH( xXmlNode.getAttribute('CLOSE_DATE')) > 0)
                then Fill_PUST( xXmlNode, Data, KodRegion, Budget, '2')
                // иначе выгружаем с типом 1 - новая
                else Fill_PUST( xXmlNode, Data, KodRegion, Budget, '1');
          // добавляем описание выгруженной организации в сист. параметр
          UpdateOldOrg( OldOrg, NULL, xXmlNode);
        end;
      end;//for//
      Result := Data.SelectNodes('PUST').length > 0;
      Data := Data.ownerDocument;
      except
        ExceptionLog( 'Сбой MakeFormat');
    end;

end;

procedure LoadDataInSysParam;
var
//  EmptySysParam: boolean;
  SysParam: Variant;
begin

    try
      WriteLog('Чтение системных параметров', True);

      OldOrg   := CreateOleObject('Microsoft.XmlDom');
      SysParam := CreateOleObject('Microsoft.XmlDom');

      EmptySysParam := QueryField('Select Id From SysParam Where Name = ?',
        ['exchange.orgcompare'], 'ID') = '';
      with SysParam.appendChild(SysParam.createElement('EXCH_UTILS_PROCESSOR')) do
      begin
        setAttribute('action', 'get_sys_param_value');
        if EmptySysParam then
          setAttribute('NAME', 'exchange.orgcompare')
        else
        with appendChild(SysParam.createElement('SYSPARAM')) do
          setAttribute('NAME', 'exchange.orgcompare');
        SysParam.loadXml(ExecXml(SysParam.xml));
        SysParam := SysParam.getElementsByTagName('PARAM_VALUE');

        if SysParam.length = 1 then
          OldOrg.loadXml(SysParam.items[0].firstChild.Data);
      end;

      except
        ExceptionLog( 'Сбой LoadDataInSysParam');
    end;

end;

procedure SaveDataInSysParam;
var
  xNodeData: Variant;
  xNodeSysParam: Variant;
  xNodeParamValue: Variant;
  xNodeReference: Variant;
  SysParam: Variant;
begin

    try
      //Запись выгруженных организаций.
      WriteLog('Сохранение данных по выгруженным организациям...');
//      OldOrg := OldOrg.ownerDocument;

      SysParam := CreateOleObject('Microsoft.XmlDom');

      xNodeReference := SysParam.createElement('REFERENCE');
      SysParam.appendChild( xNodeReference);

      xNodeReference.setAttribute('ref_name', 'SYSPARAM');
      xNodeReference.setAttribute('action', 'save');

      xNodeSysParam := SysParam.createElement('SYSPARAM');
      xNodeReference.appendChild( xNodeSysParam);

      xNodeSysParam.setAttribute('NAME', 'exchange.orgcompare');

      xNodeParamValue := SysParam.createElement('PARAM_VALUE');
      xNodeSysParam.appendChild( xNodeParamValue);

        xNodeData := SysParam.createCDATASection('');

        xNodeParamValue.appendChild( xNodeData);

              {SysParam.getElementsByTagName('PARAM_VALUE').items[0].
                firstChild.Data := OldOrg.Xml;}
              xNodeData.Data := OldOrg.Xml;
{            end;
          end;
        end;}

        ExecXml(SysParam.xml);

//      end;

      except
        ExceptionLog( 'Сбой SaveDataInSysParam');
    end;

end;

{~GetOrgRefEx
  exchange #2534 - изменение запроса.
  базовая функция: QueryWork.GetOrgRef. }
function GetOrgRefEx( OpenOrg, NotCloseOrg, OrgWithAcc, paramOrg: Boolean;
                      OrgRoles, BudgetId: String;
                      sqlText: Boolean;
                      aCfg_OrgRolesExclude: string): Variant;
var
  i: integer;
  sql, sql1: String;
  xRolePPO_All: boolean;
begin

    try
      { exchange #2546
        проверка спецправа - доступ ко всем ППО.
        это нужно делать в первую очередь, поскольку запрос и параметры
        к запросу выборки организаций формируется динамически, а объект
        для запросов используется один.
        если операцию делать позже, то получится накладка параметров,
        что приведет к непредсказуемому результату.
        exchange #2815
        если у пользователя несколько функциональных ролей, и спецправо
        "Доступ ко всем ППО" дано в нескольких из них, т.е. спец право
        по всем ролям >1, в этом случае при выгрузке доступ ко всем ППО
        не срабатывает.
        поэтому здесь проверяется на <>0, а не >=1, поскольку здесь
        проверяется строка, а не число.}
      if (QueryField(  ' select count( * ) N from USERROLES URA'
                      +' join USERROLE URB on URB.ID = URA.USERROLE_ID '
                      +' join roleright rr on rr.userrole_id=URB.id '
                      +' where URA.SYSUSER_ID = ?'
                      +' and rr.sysright_name='#39'Ppo.All'#39,
                       [ UserID],
                       'N') <> '0')
        then xRolePPO_All := TRUE
        else xRolePPO_All := FALSE;


      sql1 := ' Select O.Id, O.Caption, O.Description, O.Kadm_Code, O.Version,' +
            ' O.Close_Date, O.Open_Date, ParentOrg.Name, ParentOrg.Caption Root_Caption,' +
            ' ParentOrg.Description Root_Description, O.Taxcode, O.Kpp, O.Name, O.Caption_Empty' +
            ' From ExpBudget E' +
            ' Left Join Org O on O.Id = E.Recipient_Id' +
            ' Left Join Org ParentOrg on ParentOrg.Id = O.Manager_Id' +
            ' Join Contractor C on C.Org_Id = O.Id';

      if paramOrg then begin
        if OrgWithAcc then begin
          sql := sql1 +' Left Join OrgAccount Oa on Oa.Org_Id = O.Id'
                      +' Where O.Id <> 0 and E.Budget_Id = ? and'
                      +' Oa.OrgAcctype_Id in (5, 7, 8, 9)';
          Query.AddStr(BudgetId);
        end else begin
          sql := sql1 +' Where O.Id <> 0 and E.Budget_Id = ?';
          Query.AddStr(BudgetId);
        end;

      end else begin
        // выгрузка из справочника организаций
        sql := '';
        if OrgWithAcc then begin
          sql := StringReplace(
                        sqlOrgRefActual,
                        ' Where O.id <> 0',
                        ' Left Join OrgAccount Oa on Oa.Org_Id = O.Id'
                       +' Left Join Budget B on B.Org_Id = C.Inspector_Id'
                       +' Where O.Id <> 0 and B.Id = ? and Oa.OrgAcctype_Id in (5, 7, 8, 9)');
          Query.AddStr(BudgetId);

        end;



        if sql = '' then begin
          sql := StringReplace(
                     sqlOrgRefActual,
                     ' Where O.id <> 0',
                     ' Left Join Budget B on B.Org_Id = C.Inspector_Id'
                         +' Where O.Id <> 0 and B.Id = ?');
          Query.AddStr(BudgetId);

        end;
        if Trim(OrgRoles) = ''
          then sql := sql + ' and Orl.Id in (0,1,9,10)'
          else sql := sql + ' and Orl.Id in (' + OrgRoles + ')';
      end;



      if OpenOrg then begin
        sql := sql + ' and ((O.Open_Date is null) or (O.Open_Date <= ?))';
        Query.AddDate(Work_Date);
      end;

      case vFilterMode of
        1,4,7,9: begin
              // 1. Выгружать список актуальных организаций с пустой датой
              // закрытия либо большей по значению   даты формирования выгрузки,
              // по умолчанию  с типом изменения 1.
              // 4. Выгружать выбранные записи из  списка  организаций,
              // сформированного из актуальных записей для организаций
              // с пустой датой закрытия либо большей по значению
              // даты формирования выгрузки, по умолчанию  с типом изменения 1.
              // 7. выгружать список актуальных организаций с пустой датой
              // закрытия или значением даты закрытия больше дааты формирования
              // выгрузки, с измененным полным  или сокращенным названием,
              // следующим образом: старые записи с типом выгрузки 3, номер
              // строки 2; измененные (новые) записи с типом изменения 4, номер
              // строки 1 (в строго указанной последовательности).
              // 9. выгружать выбранные записи из сформированного
              //    списка актуальных организаций с пустой датой закрытия или
              //    значением даты закрытия больше дааты формирования выгрузки,
              //    с измененным полным  или сокращенным названием, следующим
              //    образом: старые записи с типом выгрузки 3, номер строки 2;
              //    измененные (новые) записи с типом изменения 4, номер
              //    строки 1 (в строго указанной последовательности).
              sql := sql + ' and ((O.Close_Date is null) or (O.Close_Date >= ?))';
              Query.AddDate(Work_Date);
            end;

        2,5,10: begin
              // 2.Выгружать список актуальных организаций:
              //  1) с пустой датой закрытия  или датой закрытия  большей по
              //значению даты выгрузки, по умолчанию  с типом изменения 1;
              //  2) с заполненной датой закрытия по значению меньше либо равно
              // дате выгрузки, по умолчанию с типом изменения 2 .
              // 5.Выгружать выбранные записи из списка, сформированного из
              // актуальных записей  организаций:
              //  1) с пустой датой закрытия или датой закрытия  большей по
              // значению даты выгрузки, по умолчанию  с типом изменения 1;
              //  2) с заполненной датой закрытия по значению меньше либо
              // равно дате выгрузки, по умолчанию с типом изменения 2 .
              // 8.выгружать список актуальных организаций:
              //  1) с пустой датой закрытия или значением даты закрытия
              //      больше дааты формирования выгрузки, с измененным полным
              //      или сокращенным названием, следующим образом: старые
              //      записи с типом выгрузки 3, номер строки 2; измененные
              //      (новые) записи с типом изменения 4, номер строки 1
              //      (в строго указанной последовательности).
              //  2) С заполненной датой закрытия значением меньшим либо равным
              //      дате формирования выгрузки, по умолчанию
              //      с типом изменения 2.
              // 10.выгружать выбранные записи из сформированного списка
              //    актуальных организаций: 1) с пустой датой закрытия или
              //    значением даты закрытия больше дааты формирования выгрузки,
              //    с измененным полным  или сокращенным названием, следующим
              //    образом: старые записи с типом выгрузки 3, номер строки 2;
              //    измененные (новые) записи с типом изменения 4, номер
              //    строки 1 (в строго указанной последовательности).
              //    2) С заполненной датой закрытия значением меньшим либо
              //    равным дате формирования выгрузки, по умолчанию с типом
              //    изменения 2.
              sql := sql + ' and (((O.Close_Date is null) or (O.Close_Date >= ?))'
                         + ' or (O.Close_Date is not null and O.Close_Date <= ?))';
              Query.AddDate(Work_Date);
              Query.AddDate(Work_Date);
            end;

        3,6: begin
              // 3. Выгружать список актуальных организаций с  датой закрытия
              // меньшей по значению  даты формирования выгрузки, по умолчанию
              // с типом изменения 2.
              // 6. Выгружать выбранные записи из списка, сформированного
              // из актуальных записей  организаций:с  датой закрытия
              // меньшей либо равной по значению  даты формирования выгрузки,
              // по умолчанию  с типом изменения 2.
              sql := sql + ' and (O.Close_Date is not null and O.Close_Date <= ?)';
              Query.AddDate(Work_Date);
            end;
      end;//case//

(*


      if NotCloseOrg then begin
        // фильтр: не закрытые организации
        sql := sql + ' and ((O.Close_Date is null) or (O.Close_Date >= ?))';
        Query.AddDate(Work_Date);
      end else begin
        // exchange #2632 - ФТ3
        // если в конфиге указано "Выгружать закрытые организации"=да,
        // то если "выгружать открытые организации=да", то выгружаем все
        // "открытые и закрытые" - без фильтра закрытых.
        // если "выгружать открытые организации=нет" то фильтр будет только
        // на закрытые орг.
        if NOT OpenOrg then begin
          sql := sql + ' and (O.Close_Date is not null and O.Close_Date <= ?)';
          Query.AddDate(Work_Date);
        end;
      end;
      *)



      // выбираем организации, роли которых не должны входить в указанный список.
      // если список не указан - работаем по старому.
      // тут нужно использовать только подзапрос с exists, поскольку подзапросы
      // с join orglole+orgroles уже используются и оператор:
      // ... where ... and not in orl.id in (список исключений орг ролей)
      // ничего не даст!
      if (LENGTH( aCfg_OrgRolesExclude) > 0)
        then sql := sql + FORMAT(  ' and not exists (select oo.id from orgroles oo '
                                  +'where oo.org_id=O.id and oo.orgrole_id in (%s))',
                                   [ aCfg_OrgRolesExclude]);



      { exchange #2546
        формирует подзапрос для выборки только тех бюджетов, у которых поле ППО
        есть среди разрешенных ППО в ролях зарегистрированного пользователя.
        не забываем, что еще нужно проверить спецправо: доступ ко всем ППО.
        ид.пользователя берется из глобальной переменной UserID.
        совместимо с АЦК-exchange не ниже 5.0.4.54.}
        // если спецправо "доступ ко всем ППО" отсутствует,
        // проверка всех ролей пользователя
        // на разрешенные ППО для бюджета с полным доступом!
      if NOT xRolePPO_All then begin

        sql := sql +' and O.PPO_ID in ('
                   +' select distinct rppo.ppo_id'
                   +' from USERROLES URAA'
                   +' join roleppo rppo on rppo.userrole_id= URAA.USERROLE_ID'
                   +' and rppo.access_mode=3'
                   +' where URAA.sysuser_id= ?)';
        Query.AddStr( UserID);

      end;

      // exchange #2632
      // для случая (выгружать из измененных) добавлен подзапрос:
      // только те орг. которые входят в список выгруженных.
      if (LENGTH( vFilterChangesOrgID) > 0) then begin
        sql := sql + ' and ( ' + vFilterChangesOrgID + ')';
        for i := 0 to vFilterChangesOrgIDParams.Count - 1
          do Query.AddStr( vFilterChangesOrgIDParams.Strings[ i]);
      end;

      sql := sql +' Group by O.Id, O.Caption, O.Description, O.Kadm_Code, O.Version,'
                 +' O.Close_Date, O.Open_Date, ParentOrg.Name, ParentOrg.Caption,'
                 +' ParentOrg.Description, O.Taxcode, O.Kpp, O.Name, O.Caption_Empty';


      //нужен только текст запроса. используется для выгрузки выбранных организаций
      //из списка.
      if sqlText then begin
        Result := sql;

      end else begin
        Result := CreateOleObject('Microsoft.XmlDom');
        Result.LoadXml(Query.Run(sql));

      end;
      except
        ExceptionLog( 'Сбой GetOrgRefEx');
    end;

end;


procedure OnBeforRefresh(Sender: TObject);
begin

    try
      // exchange #2534 - изменение запроса
      FmOrg.SQLText := GetOrgRefEx( par_OpenOrg,
                                    par_NotCloseOrg,
                                    param_OrgWithAcc,
                                    param_Org,
                                    OrgRoles,
                                    Budget,
                                    True,
                                    vCfg_OrgRolesExclude);
      except
        ExceptionLog( 'Сбой OnBeforRefresh');
    end;

end;

procedure Finalize(Xml: Variant);
begin
  
  // отсоединяем контролы прогресс/статус баров
  FinalizeProgressControl;
  if (Form <> NIL) then begin
    Form.Close;
    Form.Free;
  end;
  FinalizeDefault(Xml);
  FinalizeMessageValid;
  FinalizeMain;
end; {End Finalize}

begin
  Main(NULL);
end. {End Begin}
