//АЦК: Выгрузка КБК
//Постановка: Exchange #1569
uses
  'Un_KBK8',        // exchange #2473 коды цели
  'Un_Org',         // exchange #2473 коды цели
  'Un_KBK7',        // exchange #2406
  'Un_KBK6',        // exchange #2406
  'Un_KBK5',        // exchange #2406
  'Un_KBK4',        // exchange #2406
  'Un_KBK32',        // exchange #2406
  'Un_KBK36',        // exchange #2406
  'Un_KBK35',        // exchange #2406
  'Un_KBK34',        // exchange #2406
  'Un_KBK33',        // exchange #2406
  'Un_KBK31',        // exchange #2406
  'Un_KBK2',        // Exchange #2331
  'Un_KBK1',        // Exchange #2331
  'Un_Exch2629',    // exchange #2629
  'Un_Export',
  'Un_Default',
  'Un_GrantInvestmentList',
  'Un_KBK0',        // Exchange #2331
  'Un_Generator_NomTL',
  'QueryWork',
  'QueryKbk',
  'Un_SysParams',     // exchange #2412
  'QueryTools',
  'Un_MessageValidForm',
  'Un_VersionSupport',
  'Un_Schems',
  'Un_ToolsXml',
  'Un_Tools',
  'Un_ConstRef',
  'Un_Progress',
  'Un_System',
  'Un_Log',
  'Un_Debug';
  
const
  TestFileName = 'Exp_Fk_KBK.Xml';

var
  // exchange #2983
  vIspGetFromDB: boolean;
  vIspGetFromDB_TL: boolean;

procedure Main(Xml: Variant);
begin
  try
    InitializeProgressControl;
    xml := InitializeMain( xml, TestFileName);
    if VarIsNull( xml) then exit;
    if NOT CompareVersionAZK_Finance( 'Выгрузка КБК', '2.23.65.26')
      then EXIT;
    if NOT Initialize(Xml) then EXIT;
    try
      DoMain( Xml);
      // без записи - по согласованию с ДЭ
      // если нет сбоя - отображаем журнал или если все нормально - окно
      // с сообщением окончания операции.
      // если есть хоть малейшая проблема (наличие журнала info)
      // то открываем журнал *.журнал - в нем и лог и критические сообщения.
      // если файл *.журнал не найден - откроется журнал info.
      // проблема в том, что журнал info содержит только критические
      // сообщения, поэтому он нужен как информатор, а анализировать
      // придется либо журнал log или *.журнал
      if FileExists(FileInfo)
        then ShowJournalEnd
        else
          // это сообщение пока всегда.
          // в дальнейшем нужно проанализировать, что на самом деле выгружено,
          // а что нет.
          ShowMessage( 'Выгрузка завершена.');
      finally
        Finalize(Xml);
    end;
    except
      ExceptionHandler;
  end;
end;


{~DoGetIspFromDB
  exchange #2983
  ФТ-3.2
  DOL_ISP, FIO_ISP, TEL_ISP для выгрузки информации по форматам DK, WF, KD,
  FC, FD требуется анализировать параметры: «Должность исполнителя»,
  «ФИО исполнителя», «Телефон исполнителя», конфигурации выгрузки Exp_FK_KBK_new.
  Если значение хотя бы одного из перечисленных параметров непустое,
  то выше указанным полям формируемой выгрузки присваивать значения этих
  параметров соответственно.
  Иначе  перейти к анализу системного параметра «Ответственные лица»
  (Сервис – Системные параметры – Заполнение полей в документах):
  из строк с бюджетом соответствующим NAME_BUD выбираем строку с видом
  «Информация для УФК» и организацией, соответствующей параметру отправителя
  документа FROM. NAME_UCHR, если такая строка определена однозначно
  и хотя бы одно поле блока «Ответственный исполнитель» непустое, то выше
  перечисленным полям присвоить значения полей «Должность», «ФИО», «Телефон»
  блока «Ответственный исполнитель» соответственно.
  Иначе в справочнике «Ответственные лица» из строк с организацией,
  определенной для FROM. NAME_UCHR, и включенным признаком «Актуальное»
  выбрать строку с типом должности, имеющей номер «-5», для которой хотя
  бы одно из значений полей «Должность», «ФИО», «Телефон» непустое и присвоить
  вышеуказанным полям  соответственно.
  Если не удалось определить значение полей ответственного исполнителя,
  то данные поля не заполнять, продолжать выгрузку.
  при инициализации проверяются параметры из конфига один раз.
  здесь значения загружаются из бд для орг aOrgID. }
procedure DoGetIspFromDB( aOrgID, aOrgName: string);

  {~WriteInfoOrgName_
    вывод сообщения в журнал info с добавлением к сообщению названия
    организации aOrgName.
    это нужно при отображении ошибки определения исполнителя.}
  procedure WriteInfoOrgName_( aMsg: string);
  begin
    WriteInfo( FORMAT( aMsg, [ aOrgName]));
  end;

  {~WriteLogOrgName_
    вывод сообщения в журнал info с добавлением к сообщению названия
    организации aOrgName.
    это нужно при отображении информации определения исполнителя.}
  procedure WriteLogOrgName_( aMsg: string);
  begin
    WriteLog( FORMAT( aMsg, [ aOrgName]));
  end;

  {~IsISP_Filled_
    проверка заполнения всех полей испольнителя.
    возвращает:
      =true - если одно из полей заполнено
      =false - любое все поля пустые}
  function IsISP_Filled_: boolean;
  begin

      try
        Result := (    ( LENGTH( TRIM( TelIsp)) > 0)
                   AND ( LENGTH( TRIM( FioIsp)) > 0)
                   AND ( LENGTH( TRIM( DolIsp)) > 0));
        except
          ExceptionProc( 'IsISP_Filled_');
      end;

  end;

var
  xXml: Variant;
  xFound: integer;
begin

    try
      if NOT vIspGetFromDB then begin
        WriteLogOrgName_( 'Исполнитель для организации %s заполняется по конфигурации.');
        EXIT;
      end;

      // ищем в системных параметрах
      if SysParam_RESPONSIBLEPERSONS_Details( BudgetId,
                                              '',
                                              aOrgID,
                                              '-52',  // нужен телефон
                                              '2',  // информация для УФК
                                              // телефон заполняется вместо ID
                                              TelIsp,
                                              FioIsp,
                                              DolIsp) then
        if IsISP_Filled_ then begin
          WriteLogOrgName_( 'Исполнитель для организации %s заполнен из системных параметров.');
          EXIT;
        end;

        // не нашли - ищем в справ. "ответственные лица" с флагом "Актуальная"

        xXml := QueryXml(
                      ' Select Rp.* from RespPerson Rp'
                     +' Left Join Appointment A on Rp.Appointment_Id = A.Id'
                     +' Where Rp.Org_Id = ? and A.Id = ? and Rp.ACTUAL_FLAG=1',
                      [ aOrgID,
                        '-5']);
        xFound := xXml.documentElement.childNodes.Length;
        if (xFound = 1) then begin
          xXml := xXml.documentElement.childNodes.Item[0];
          // exchange #2901
          // нужно чтобы выгружался не тип должности из справ.должностей,
          // а наименование из ответственного исполнителя.
          // поэтому вместо A.CAPTION будем выгружать Pr.APPOINTMENT_NAME
          DolIsp := xXml.getAttribute('APPOINTMENT_NAME');
          FioIsp := xXml.getAttribute('FIO');
          TelIsp := xXml.getAttribute('PHONE');
          if IsISP_Filled_
            then WriteLogOrgName_( 'Исполнитель для организации %s заполнен из справочника.')
            else WriteInfoOrgName_( 'Исполнитель для организации %s не заполнен.');
          EXIT;
        end;

      if (xFound <= 0)
        then WriteInfoOrgName_( 'Исполнитель для организации %s не найден в БД - не заполнен.')
        else WriteInfoOrgName_( 'Найдено несколько исполнителей для организации %s в БД - не заполнен.');
      except
        ExceptionProc( 'DoGetIspFromDB');
    end;

end;



{~DoMain
  операция выгрузки.

  без записи
  главная операция вынесена в отдельную подпрограмму.
  это упрощает обработку результата (более наглядно):
    либо журнал сообщений либо обработка исключений.}
procedure DoMain(Xml: Variant);
var
  xFO_ID: string;
  xSL: TStringList;
  xResultCode: integer;
  xFileOut: string;
  xQuery: TRunQuery;
  xSQL: string;
  xOrgName, xOrgDesc: string;
begin

    try


      if (param_ShowFormParam) and (not fm_ModalResultOk) then
      begin
        //ShowMessage('Выгрузка прервана.');
        WriteInfo('Выгрузка прервана пользователем');
        Exit;
      end;

      //сюда дописывать логические параметры по выгрузке кодов, если все параметры
      //не установлены, то досвидания.
      if (not UnloadKbk) and (not UnloadGlava) and (not UnloadKcsr)
          and (not UnloadKvr) and (not UnloadSubDohIst)
          and (not UnloadGrantInvestment)
          AND (NOT vUnloadPurposefulgrant) then begin
        ShowMessage('Ни одного параметра выгрузки кодов не установлено.');
        Exit;
      end;

      if (UnloadSubDohIst) and (AzkCompareVersion('2.25.0.33') < 0) then
        Raise('Выгрузка справочников кодов видов источников, кодов подвидов' +
          ' доходов доступна с версии АЦК-Финансы 2.25.0.33.' + #13#10 +
          'Установите параметр "Выгружать справочник коды видов источников,' +
          ' коды подвидов доходов" = Нет, или обновите систему');
{
      if (UnloadGrantInvestment) and (AzkCompareVersion('2.27.2.48') < 0) then
        Raise('Выгрузка справочника целевых субсидий доступна с версии АЦК-Финансы 2.27.2.48.' + #13#10 +
          'Установите параметр "Выгружать справочник целевых субсидий" = Нет, или обновите систему');
}

      // начало экспорта - инициализируем номер операции экспорта для
      // ведения нумерации учредителей
      InializeActionExportNomTL( DirIni);

      Form.Caption := 'Выгрузка справочников КБК';
      Form.Show;
      Form.Width := 500;
      WriteLog('Чтение системных параметров', True);
      SysParam := CreateOleObject('Microsoft.XmlDom');
      OldKbk := CreateOleObject('Microsoft.XmlDom');
      EmptySysParam := QueryField('Select Id From SysParam Where Name = ?',
        ['exchange.kbkcompare'], 'ID') = '';


      with SysParam.appendChild(SysParam.createElement('EXCH_UTILS_PROCESSOR')) do
      begin
        setAttribute('action', 'get_sys_param_value');
        if EmptySysParam then
          setAttribute('NAME', 'exchange.kbkcompare')
        else
        with appendChild(SysParam.createElement('SYSPARAM')) do
          setAttribute('NAME', 'exchange.kbkcompare');
        SysParam.loadXml(ExecXml(SysParam.xml));
        SysParam := SysParam.getElementsByTagName('PARAM_VALUE');
        if SysParam.length = 1 then
          OldKbk.loadXml(SysParam.items[0].firstChild.Data);
      end;
      if EmptySysParam then
      begin
        OldKbk := CreateOleObject('Microsoft.XmlDom');
        OldKbk.appendChild(OldKbk.createElement('EXCHQUERY'));
        OldKbk.documentElement.setAttribute('completed', 'true');
      end;
      OldKbk := OldKbk.documentElement;

      // продолжить
      WriteLog('Получение списка бюджетов', True);

      { exchange #2546
        формирует подзапрос для выборки только тех бюджетов, у которых поле ППО
        есть среди разрешенных ППО в ролях зарегистрированного пользователя.
        подзапрос формируется,только если опция конфига param_UnloadKbkFromAllBudget
        (Выгружать КБК из всех бюджетов)=Да.
        не забываем, что еще нужно проверить спецправо: доступ ко всем ППО.
        ид.пользователя берется из глобальной переменной UserID.
        совместимо с АЦК-exchange не ниже 5.0.4.54.}

      if param_UnloadKbkFromAllBudget
        then BudgXml := QueryXml(  'Select b.* From Budget b '
                                  +' Where b.FinYear = ?'
                                  // проверка спецправа - доступ ко всем ППО
                                  +' and (exists ('
                                  +'        select rr.* 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')'
                                  // проверка всех ролей пользователя
                                  // на разрешенные ППО для бюджета с полным доступом!
                                  +'      or (B.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= ?))'
                                  // так же принудительно добавляем текущий бюджет
                                  // без проверки у него ППО - это не нужно.
                                  // проверять нужно все, включая прилогиненный.
//                                  +'      or (b.id = ?)'
                                  +')',
                                  [ Budget_Year,
                                    UserID,
                                    UserID])
//                                    Budget_Id])
        else BudgXml := QueryXml( sqlBudget + ' Where id = ?', [Budget_Id]);
//      BudgXml := QueryXml(sqlBudget + ' Where FinYear = ?', [Budget_Year]);
      BudgXml := BudgXml.documentElement;


      //Exchange #2039
      if (not EmptySysParam) and (param_ChangePrevYear)
          and (param_VariantUnloadKBK = 'только измененные строки') then
      begin
        if param_UnloadKbkFromAllBudget then
          //проставляем соответтствие для всех бюджетов
          MappBudgXml := QueryXml(
            ' Select Ic.Budget_Id NewBudget_Id, Ic.Old_Budget_Id OldBudget_Id' +
            ' From IdChanges Ic' +
            ' Join Budget B on B.Id = Ic.Budget_Id' +
            ' Where Ic.TableName = ''BUDGET'' and B.FinYear = ?', [Budget_Year])
        else
          //соответствие только для прилогиненного бюджета
          MappBudgXml := QueryXml(
            ' Select Ic.Budget_Id NewBudget_Id, Ic.Old_Budget_Id OldBudget_Id' +
            ' From IdChanges Ic' +
            ' Where Ic.TableName = ''BUDGET'' and Ic.Budget_Id = ?', [Budget_Id]);
        MappBudgXml := MappBudgXml.documentElement;
        //удаляем ноды, в которые будут переименованы по соответсвию ноды бюджетов
        //прошлых лет.
        for i := 0 to MappBudgXml.ChildNodes.Length - 1 do
        begin
          idx := 0;
          while idx <= OldKbk.ChildNodes.Length - 1 do
            if ('B' + MappBudgXml.ChildNodes.Item[i].getAttribute('OLDBUDGET_ID') =
              OldKbk.ChildNodes.Item[idx].NodeName) then
            begin
              //удаляем возможно уже существующий Node с именем переименовывае-
              //мого Node
              RemoveNode(OldKbk.ChildNodes.Item[idx],
                'B' + MappBudgXml.ChildNodes.Item[i].getAttribute('NEWBUDGET_ID'));
              Break;
            end
            else
              Inc(idx);
        end;
        for i := 0 to MappBudgXml.ChildNodes.Length - 1 do
        begin
          idx := 0;
          while idx <= OldKbk.ChildNodes.Length - 1 do
            if ('B' + MappBudgXml.ChildNodes.Item[i].getAttribute('OLDBUDGET_ID') =
              OldKbk.ChildNodes.Item[idx].NodeName) then
            begin
              //переименовывем Node
              RenameNode(OldKbk.ChildNodes.Item[idx],
                'B' + MappBudgXml.ChildNodes.Item[i].getAttribute('NEWBUDGET_ID'));
              Break;
            end
            else
              Inc(idx);
        end;
      end;

      { exchange #2546
        вариант, когда не найдено ни одного бюджета для выгрузки.}
      if (BudgXml.childNodes.Length <= 0) then begin
        WriteLog('Не найдено ни одного бюджета для выгрузки.');
        ShowMessage( 'Не найдено ни одного бюджета для выгрузки.');
        EXIT;
      end;

      OldKbk := OldKbk.ownerDocument.documentElement;
      BudgIndx := 0;
      repeat
        // рабочая дата - выбранная на форме при соединении с сервером
        

        if not param_UnloadKbkFromAllBudget
          then BudgetId := Budget_Id
          else BudgetId := BudgXml.childNodes.Item[BudgIndx].getAttribute('ID');
        

        //ищем нужный бюджет в значении из sysparam
        NodeXmlList := OldKbk.SelectNodes('B' + BudgetId);
        if (NodeXmlList.Length = 0) then
          OldKbk := OldKbk.appendChild(OldKbk.ownerDocument.createElement('B' + BudgetId))
        else
          OldKbk := NodeXmlList.Item[0];
        Data := CreateOleObject('Microsoft.XmlDom');
        Data.appendChild(Data.createElement('FORMAT'));

        WriteLog('Определение данных Финансового органа', True);
        Fo := GetOrgByBudget(BudgetId);
        if (Fo.documentElement.childNodes.Length <> 1)
          then Fo := QueryXml(sqlOrgByBudget, [BudgetId]);
        Fo := Fo.documentElement.childNodes.Item[0];
        // exchange #2983
        // на нужно получить ид.орг ФО
        xFO_ID := GetNodeAttributeEx( Fo, 'ID');

        // exchange #2983
        // если поля исполнителя по конфигу пустые, то загружаем поля из бд
        DoGetIspFromDB( xFO_ID, GetNodeAttributeEx( Fo, 'DESCRIPTION'));

        WriteLog('Определение данных ТОФК (ОрФК)', True);
        // по этим данным при выгрузке заполняются KOD_TOFK и NAME_TOFK в разделе TO
        
        // Exchange, #3217      ТОФК м.б. из парам. конфига
        if (LENGTH( TRIM( vCfg_OrgID_FK)) > 0) then
        begin
          //подменим в запросе код и наименование орг-и на те, что уже ЯВНО выбраны юзером в конфиге
          //ситуация, что в справочнике орг-я изменит наименование, в принципе возможна
          xOrgName := Copy(vCfg_Org_FK, 1, Pos(',', vCfg_Org_FK) - 1); //код ОрФК по идее д.б. = 4
          xOrgDesc := Copy(vCfg_Org_FK, length(xOrgName) + 2, length(vCfg_Org_FK));

          xQuery:= TRunQuery.Create;
          try
            xSQL := 'Select o.id, O.Taxcode, O.Kpp, o.caption, ''' + xOrgName + ''' as name, ''' +
                     xOrgDesc + ''' as description, O.Kadm_Code from org O where o.id=?';
            xQuery.AddStr( vCfg_OrgID_FK);
            Ufk := QueryRun( xQuery, xSQL);
            
          finally
            xQuery.Free;
          end;
        end;
        if Ufk.documentElement.childNodes.Length <> 1 then
          Ufk := GetTofkByBuh(BudgetId);  //подтягиваем ТОФК (ОрФК) из АЦК

        if Ufk.documentElement.childNodes.Length = 0 then
          Raise('Не удалось получить реквизиты организации ТОФК. ' +
          'Проверьте заполение счета в АЦК "Бухгалтерия-Настройка счетов-Счета ФО-' +
          'Основной бюджетный счет".');
        Ufk := Ufk.documentElement.childNodes.Item[0];
        //обнуляем кэш
        CacheFormatKbk.Clear;
        
        //заполнение содержимого файла.
        Form.Caption := 'Выгрузка справочников по кодам бюджетной классификации';
        if UnloadKbk then begin
          IniSection := 'КБК';
          DoExportKBK( BudgetID, param_OutListKbk);


//            HaltEx( 'Остановка выполнения скрипта после выгрузки КБК.', true);
        end; //if UnloadKbk then
        
        if UnloadKcsr then begin
          IniSection := 'КЦСР';
          DoExportKCSR( BudgetID, param_ref, param_OutListKbk);
        end;
        if UnloadKvr then
        begin
          IniSection := 'КВР';
          DoExportKVR( BudgetID, param_ref, param_OutListKbk);
        end; //if UnloadKvr then
        if UnloadGlava then
        begin
          IniSection := 'ГЛАВА';
          DoExportGLAVA( BudgetID, param_ref, param_OutListKbk);
        end; //if UnloadGlava then
        if UnloadSubDohIst then
        begin
          IniSection := 'ПОДВИДДОХИСТ';
          DoExportSubDohIst( BudgetID, param_ref, param_OutListKbk, param_ModeSubDohIst);
        end; //if UnloadSubDohIst then

        // exchange #2473
        if vUnloadPurposefulgrant then begin
          IniSection := 'КОДЫ_ЦЕЛИ';
          DoExportPurposefulgrant(  BudgetID,
                                    param_ref,
                                    param_OutListKbk,
                                    vCfg_2851);
        end;

        if UnloadGrantInvestment then
        begin
          // Exchange #2334
          // выбираем список субсидий и инвестиций из справочника.
          // в результате формируется список ID через запятую
          // exchange #2820
          // добавлен учредитель из конфига.
          // если указан. ид.орг.учредителя, то список субсидий
          // выбирается только для этой организации.
          // в результате, затем, когда потребуется разделить выбранные
          // выгружаемые коды по учредителям будет выбрана толькоодна
          // организация для всех выгружаемых кодов субсидий.
          // exchange #3027
          // добавлен параметр: выбор кодок из списка + дата выгрузки.
          vFilterGrandInvesment := ChoiceGrantInvestmentListEx(
                                        DirIni,
                                        BudgetId,
                                        vCfg_OrgID_Uchr,
                                        param_OutListKbk,
                                        (param_VariantUnloadKBK = 'все строки'));
          if (vFilterGrandInvesment = '') then begin
            // без записи
            WriteInfo('Выгрузка прервана пользователем');
            //ShowMessage( 'Отменено пользователем.');
            Exit;
          end;
    //      ShowMessage( 'Filter = "' + vFilterGrandInvesment + '"');
    //      exit;

          IniSection := 'СУБСИДИЯ';
          // разбиение по учредителям
          Uchr := GetUchrGrantInvestment(BudgetId, vFilterGrandInvesment);
          Uchr := Uchr.documentElement;
          //создаем xml c текущей историей из базы
          CurrHistory := CreateOleObject('Microsoft.XmlDom');
          CurrHistory := CurrHistory.appendChild(CurrHistory.createElement('HISTORY'));
          CurrHistory := CurrHistory.appendChild(CurrHistory.ownerDocument.createElement('GRANTINV'));
          //ищем node GRANTINV
          NodeXmlList := OldKbk.selectNodes('GRANTINV');
          if NodeXmlList.Length = 0 then
            OldKbk := OldKbk.appendChild(OldKbk.ownerDocument.createElement('GRANTINV'))
          else
            OldKbk := NodeXmlList.Item[0];
          for j := 0 to Uchr.ChildNodes.Length - 1 do
          begin
            Uchr := Uchr.ChildNodes.item[j];
            // exchange #3027
            // поскольку теперь требуется передать на сервер список ид.кодов
            // то здесь заводим список, и в случае успешной выгрузки
            // передедадим этот список на сервер АЦК-Ф.
            // это удобнее, чем реализовывать список внутри функции.
            xSL := TStringList.Create;
            try
              case MakeFormatGrantInvestment( xSL) of
                // выгрузка не выполнена
                // нужно сохранить выгруженные коды кбк
               -1: BREAK;
                1: begin    // выгрузка выполнена успешно

                    //валидация схем.
                    if param_CtlValidOn then
                      if not ValidateFormat then
                      begin
                      if Not ShowMessageValid( FileValidate) then
      //                MessageValidText.Lines.LoadFromFile( FileValidate);
        //              if ( Form_MessageValid.ShowModal = mrCancel) then
      {
                        ShellExecute('NotePad', FileValidate, False);
                        WriteLog('Не пройден контроль валидности отдельными документами');
                        if MessageDlg('Не пройден контроль валидности.' + cr +
                          'Продолжить выгрузку?', mtConfirmation, mbYes + mbNo, 0) = mrNo then
      }
                        begin
                          //сохраняем измененые данные по другим КБК перед выходом
                          SaveDataInSysParam;
                          WriteInfo('Выгрузка прервана пользователем');
                          Exit;
                        end;
                      end;

                    // exchange #3130 - поправлено имя файла выгрузки
                    xFileOut := DirOut + FileOut;
                    try
                      WriteLog('Сохранение в исходящем каталоге: ' + xFileOut, True);
                      XmlToTextFile(xFileOut);
                      //Запись номера посылки в течении дня
                      with TIniFile.Create(DirIni + 'Schems\Send.dat') do begin
                        WriteString(IniSection, FormatDateTime('yyyymmdd', Now),
                        IntToStr(PacketNumber));
                        Free;
                      end;
                      // exchange #3027
                      // здесь фиксируется только дата выгрузки.
                      // требуется совместимость с АЦК-Ф 2.38 не ниже сборки 77.
                      // если будет сбой, то файл выгрузки будет стерт, и будет
                      // сообщение в журнале, поэтому здесь прерываем
                      // копирование файла и переходим к след.итерации
                      // exchange #3130 - поправлено имя файла
                      FixedExportedListIDEx( xFileOut, xSL, 'gi');
                    except
                      raise('Ошибка сохранения: ' + xFileOut);
                    end;
                    if FileExists( xFileOut) then begin
                      //сохраняем данные по выгруженным КБК в Xml для sysparam
                      NodeXmlList := OldKbk;
                      OldKbk := OldKbk.parentNode;
                      OldKbk.ReplaceChild(CurrHistory, NodeXmlList);
                      CurrHistory := CurrHistory.parentNode;
                    end else begin
                      // сбой изменения даты выгрузки - файл и кэш не сохраняем
                      NodeXmlList := OldKbk;
                      OldKbk := OldKbk.parentNode;
                      OldKbk.ReplaceChild(CurrHistory, NodeXmlList);
                    end;
                  end;

                // выгрузка не выполнена
                else begin
                  NodeXmlList := OldKbk;
                  OldKbk := OldKbk.parentNode;
                  OldKbk.ReplaceChild(CurrHistory, NodeXmlList);
                end;
              end;//case//
              finally
                xSL.Free;
            end;
            Uchr := Uchr.parentNode;
          end;
        end; //if UnloadGrantInvestment then
        Inc(BudgIndx);
        OldKbk := OldKbk.ownerdocument.documentElement;
      until (BudgIndx > BudgXml.childNodes.Length - 1) or (not param_UnloadKbkFromAllBudget);
      //сохраняем данные выгруженных КБК
      SaveDataInSysParam;
      except
        ExceptionLog( 'Сбой DoMain');
    end;

end;

function MakeFormatGrantInvestment( aListID: TStringList): integer;
var
  i: Integer;
  XmlOrg, Budget, Xml: Variant;
  sql, UfkId: String;
  xKBK_List: variant;
  xNomTL: string;
//  xErrors: boolean;
  xNAME_RUK: string;
  xNAME_RUK_FES: string;
  xDOL_ISP_1: string;
  xFIO_ISP_1: string;
  xTEL_ISP_1: string;
  xUchrID: string;
  xUchrName: string;
  xCodeOut: boolean;

  {~WriteInfoUchrName_
    вывод сообщения в журнал info с добавлением к сообщению названия
    организации учредителя.
    это нужно при отображении ошибки определения ФИО бух/дир/исполнителя.}
  procedure WriteInfoUchrName_( aMsg: string);
  begin
    WriteInfo( FORMAT( aMsg, [ xUchrName]));
  end;

  {~WriteLogUchrName_
    вывод сообщения в журнал info с добавлением к сообщению названия
    организации учредителя.
    это нужно при отображении информации определения ФИО бух/дир/исполнителя.}
  procedure WriteLogUchrName_( aMsg: string);
  begin
    WriteLog( FORMAT( aMsg, [ xUchrName]));
  end;

  {~GetFIO_by2983_
   exchange #2983
   ФТ-2
   значение полей  TL.NAME_RUK и TL.NAME_RUK_FES определять из системного
   параметра «Ответственные лица» (Сервис – Системные параметры –
   Заполнение полей в документах) следующим образом:  из строк с бюджетом
   соответствующим TL. NAME_BUD выбираем строку с видом «Информация для УФК»
   и организацией, соответствующей параметру отправителя документа
   FROM.NAME_UCHR, если такая строка определена однозначно, то полям
   TL.NAME_RUK и TL.NAME_RUK_FES присвоить значения полей «ФИО» блока
   руководителя и «ФИО» блока «Бухгалтер» соответственно.
   Если значения требуемых полей из системного параметра вычислить не удалось,
   то в справочнике «Ответственные лица» из строк с организацией,
   определенной для FROM. NAME_UCHR, и включенным признаком «Актуальное»
   выбрать строку с типом должности, имеющей номер «-2», значение поля «ФИО»
   которой присвоить полю  TL.NAME_RUK, и строку с типом должности, имеющей
   номер «-3» (поправка exchange #2983), значение поля «ФИО» которой присвоить
   полю  TL.NAME_RUK_FES.
   Если однозначно одно из значений определить не удалось, то выдавать
   неигнорируемую ошибку выгрузки – значение поля NAME_RUK (или/и NAME_RUK_FES)
   определить не удалось. Формирование выгрузки прекращать.
   здесь возвращаем только ФИО для aFIO_Type:
     =-2 - руководитель
     =-3 - бухгалтер
     прочее - не допустимо.}
  function GetFIO_by2983_( aFIO_Type: string): string;
  var
    xResID, xResFIO, xResPOST: string;
    xFound: string;
  begin

      try
        Result := '';
        // ищем в системных параметрах
        if SysParam_RESPONSIBLEPERSONS_Details( BudgetId,
                                                '',
                                                xUchrID,
                                                aFIO_Type,
                                                '2',  // информация для УФК
                                                xResID,
                                                xResFIO,
                                                xResPOST)
          then begin
            Result := TRIM( xResFIO);
            if ( LENGTH( Result) > 0) then begin
              case aFIO_Type of
                '-2': WriteLogUchrName_( 'ФИО руководителя для организации %s заполнено из системного параметра.');
                '-3': WriteLogUchrName_( 'ФИО Бухгалтера для организации %s заполнено из системного параметра.');
              end;
              EXIT;
            end;
          end;

        // не нашли - ищем в справ. "ответственные лица" с флагом "Актуальная"
        // в начале проверяем количество записей в бд

        xFound := QueryField(
                        ' Select count( * ) S from RespPerson Rp'
                       +' Left Join Appointment A on Rp.Appointment_Id = A.Id'
                       +' Where Rp.Org_Id = ? and A.Id = ? and Rp.ACTUAL_FLAG=1',
                        [ xUchrID,
                          aFIO_Type],
                        'S');
        if (xFound <> '1') then begin
//          xErrors := TRUE;
          case aFIO_Type of
            '-2': if (xFound = '0')
                    then WriteInfoUchrName_( 'Не найдено актуального ФИО руководителя для организации %s.')
                    else WriteInfoUchrName_( 'Не найдено однозначно актуального ФИО руководителя для организации %s.');
            '-3': if (xFound = '0')
                    then WriteInfoUchrName_( 'Не найдено актуального ФИО Бухгалтера для организации %s.')
                    else WriteInfoUchrName_( 'Не найдено однозначно актуального ФИО Бухгалтера для организации %s.');
          end;
          EXIT;
        end;
        // если найдено однозначно - берем значение

        Result := TRIM( QueryField(
                            ' Select Rp.FIO S from RespPerson Rp'
                           +' Left Join Appointment A on Rp.Appointment_Id = A.Id'
                           +' Where Rp.Org_Id = ? and A.Id = ? and Rp.ACTUAL_FLAG=1',
                            [ xUchrID,
                              aFIO_Type],
                            'S'));
        // проверяем заполнение
        if ( LENGTH( Result) <= 0) then begin
//          xErrors := TRUE;
          case aFIO_Type of
            '-2': WriteInfoUchrName_( 'ФИО руководителя заполнено пустым значением (организация %s).');
            '-3': WriteInfoUchrName_( 'ФИО Бухгалтера заполнено пустым значением (организация %s).');
          end;
          EXIT;
        end;

        case aFIO_Type of
          '-2': WriteLogUchrName_( 'ФИО руководителя для организации %s заполнено из справочника.');
          '-3': WriteLogUchrName_( 'ФИО Бухгалтера для организации %s заполнено из справочника.');
        end;
        except
          ExceptionProc( 'GetFIO_by2983_');
      end;

  end;

  {~IsISP_Filled_
    проверка заполнения всех полей испольнителя.
    возвращает:
      =true - если все поля заполнены
      =false - любое из полей пустое}
  function IsISP_Filled_: boolean;
  begin

      try
        Result := (( LENGTH( TRIM( xTEL_ISP_1)) > 0)
                   AND ( LENGTH( TRIM( xFIO_ISP_1)) > 0)
                   AND ( LENGTH( TRIM( xDOL_ISP_1)) > 0));
        except
          ExceptionProc( 'IsISP_Filled_');
      end;

  end;

  {~GetISP_by2983_
    exchange #2983
    ФТ-3.1
    DOL_ISP_1, FIO_ISP_1, TEL_ISP_1 для выгрузки информации по форматам TL из
    «Ответственные лица» (Сервис – Системные параметры – Заполнение полей в
    документах):  из строк с бюджетом соответствующим NAME_BUD выбираем строку
    с видом «Информация для УФК» и организацией, соответствующей параметру
    отправителя документа FROM.NAME_UCHR, если такая строка определена
    однозначно и все поля блока «Ответственный исполнитель» непустые,
    то выше перечисленным полям присвоить значения полей «Должность», «ФИО»,
    «Телефон» блока «Ответственный исполнитель» соответственно.
    Иначе в справочнике «Ответственные лица» из строк с организацией,
    определенной для FROM. NAME_UCHR, и включенным признаком «Актуальное»
    выбрать строку с типом должности, имеющей номер «-5», для которой все
    значения полей «Должность», «ФИО», «Телефон» непустые и присвоить
    вышеуказанным полям  соответственно.
    Если однозначно определить значения не удалось, то выдавать неигнорируемую
    ошибку выгрузки – значение полей ответственного исполнителя определить
    не удалось.}
  procedure GetISP_by2983_;
  var
    xFound: integer;
    xXml: Variant;
  begin

      try
        xTEL_ISP_1 := '';
        xFIO_ISP_1 := '';
        xDOL_ISP_1 := '';

        if NOT vIspGetFromDB_TL then begin
          xTEL_ISP_1 := TelIsp;
          xFIO_ISP_1 := FioIsp;
          xDOL_ISP_1 := DolIsp;
          WriteLogUchrName_( 'Исполнитель для организации %s заполняется по конфигурации.');
          EXIT;
        end;

        // ищем в системных параметрах
        if SysParam_RESPONSIBLEPERSONS_Details( BudgetId,
                                                '',
                                                xUchrID,
                                                '-52',  // нужен телефон
                                                '2',  // информация для УФК
                                                // телефон заполняется вместо ID
                                                xTEL_ISP_1,
                                                xFIO_ISP_1,
                                                xDOL_ISP_1) then
          if IsISP_Filled_ then begin
            WriteLogUchrName_( 'Исполнитель для организации %s заполнен из системных параметров.');
            EXIT;
          end;

        // не нашли - ищем в справ. "ответственные лица" с флагом "Актуальная"

        xXml := QueryXml(
                      ' Select Rp.* from RespPerson Rp'
                     +' Left Join Appointment A on Rp.Appointment_Id = A.Id'
                     +' Where Rp.Org_Id = ? and A.Id = ? and Rp.ACTUAL_FLAG=1',
                      [ xUchrID,
                        '-5']);
        xFound := xXml.documentElement.childNodes.Length;
        if (xFound = 1) then begin
          xXml := xXml.documentElement.childNodes.Item[0];
          WriteLogUchrName_( 'Исполнитель для организации %s заполнен из справочника.');
          // exchange #2901
          // нужно чтобы выгружался не тип должности из справ.должностей,
          // а наименование из ответственного исполнителя.
          // поэтому вместо A.CAPTION будем выгружать Pr.APPOINTMENT_NAME
          xDOL_ISP_1 := xXml.getAttribute('APPOINTMENT_NAME');
          xFIO_ISP_1 := xXml.getAttribute('FIO');
          xTEL_ISP_1 := xXml.getAttribute('PHONE');
          // проверяем что заполнено и отображаем в журнале что незаполнено
          // это делается на случай, если валидация отключена.
          if NOT IsISP_Filled_ then begin
            if ( LENGTH( TRIM( xTEL_ISP_1)) <= 0)
              then WriteInfoUchrName_( 'Не указан телефон исполнителя для организации %s.');
            if ( LENGTH( TRIM( xFIO_ISP_1)) <= 0)
              then WriteInfoUchrName_( 'Не указано ФИО исполнителя для организации %s.');
            if ( LENGTH( TRIM( xDOL_ISP_1)) <= 0)
              then WriteInfoUchrName_( 'Не указана должность исполнителя для организации %s.');
          end;
          EXIT;
        end;

        if (xFound <= 0)
          then WriteInfoUchrName_( 'Не найдено актуального исполнителя для организации %s.')
          else WriteInfoUchrName_( 'Не найдено однозначно актуального исполнителя для организации %s.');
        except
          ExceptionProc( 'GetISP_by2983_');
      end;

  end;

begin

    try
  Result := 0;

  // exchange #2983
  // получаем значения ФИО по записи еще перед началом формирования файлы TL.
  // при этом проверяем оба поля и в случае невозможности получить значения
  // ФИО по одному из полей - пишем в журнал сообщение и выгрузку не выполняем.
  // жеткая отмена выгрузки заменена на валидацию (согласовано с АО).
  xUchrID := Uchr.getAttribute( 'ORG_ID');
  xUchrName := Uchr.getAttribute('DESCRIPTION');
//  xErrors := FALSE;
  xNAME_RUK     := GetFIO_by2983_( '-2');
  xNAME_RUK_FES := GetFIO_by2983_( '-3');
  GetISP_by2983_;
{  if xErrors then begin
    WriteInfo( 'Выгрузка недопустима.');
    Result := -1;
    EXIT;
  end;}

  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;
  //Загрузка схемы
  // Exchange 2336 загружаем схему в зависимости от версии
  // exchange #2542 - переход на тфф11 + переработка модуля проверки версий схем
  LoadSchema( GetFileNameSchemaTFF( 'tl_txtl*'));
//  LoadSchema( DirIni + VersionSchemsDir + 'tl_txtl120101.xml');
  WriteLog('Формирование имени выгружаемого файла', True);
  //имя файла формируется по коду учредителя
  FileOut := GetFileName(Uchr.getAttribute('ORG_CODE'), 'TL', Now, PacketNumber,
    FileOutIsLocalNet, FileOutOrgCodeIsFk);
  Data := Data.documentElement;
  //ищем node ORGID_
  NodeXmlList := OldKbk.selectNodes('ORGID_' + Uchr.getAttribute('ORG_ID'));
  if NodeXmlList.Length = 0 then
    OldKbk := OldKbk.appendChild(OldKbk.ownerDocument.createElement('ORGID_' + Uchr.getAttribute('ORG_ID')))
  else
    OldKbk := NodeXmlList.Item[0];
  CurrHistory := CurrHistory.appendChild(CurrHistory.ownerDocument.createElement('ORGID_' + Uchr.getAttribute('ORG_ID')));
  WriteLog('Заполнение данных файла: ' + FileOut, True);
  Data := Data.appendChild(FillElementBySchema(Uchr, 'FK'));
  Data.setAttribute('FORM_VER', AppVersion);
  Data := Data.parentNode;
  Data := Data.appendChild(FillElementBySchema(Uchr, 'FROM'));
  Data.setAttribute('BUDG_LEVEL', GetBudgLevel(BudgetId));
  Data := Data.parentNode;
  sql := 'Select O.*, Coalesce(Ob.Code_Fk, O.Name) Org_Code' +
    ' From Org O' +
    ' Left Join OrgBudgetDet Ob on Ob.Org_Id = O.Id and Ob.Budget_Id = ?' +
    ' Where O.Id = ?';
  // exchange #2724
  // при проверке параметра возможное значение может быть =NULL.
  // этот вариант небыл учтен. поправлено на преобразование к пустой строке.
  UfkId := SysParam_UFG_OrgID(BudgetId);



  { без записи
    здесь вместо формирования исключения, возвращаем -1, что означает
    прерывание выгрузки, а сообщение пишем в журнал info.}
  if UfkId = '' then begin
    WriteInfo( 'Не удалось получить организацию УФК.'
              +' Проверьте заполнение организации в системных параметрах,'
              +' ветка УФК.');
    WriteInfo( 'Выгрузка прервана.');
    Result := -1;
    EXIT;
  end;

  Ufk := QueryXml(sql, [BudgetId, UfkId]);
  Ufk := Ufk.documentElement.childNodes.item[0];
  Data.appendChild(FillElementBySchema(Ufk, 'TO'));
  Data := Data.appendChild(FillElementBySchema(Uchr, 'TL'));

  // Exchange #2334
  // необходимо для каждого учредителя, при выгрузке, создавать новый номер.
  // этот номер клиент сверяет со своим номером(в диалоге).
  // при каждой выгрузке необходимо создавать свои номера учреждений.
  // в результате, этот номер записывается  поле TL.NOM_TL вместо PacketNumber.
  xNomTL := GetExportNomTL( DirIni,
                            BudgetId,
                            xUchrID,//Uchr.getAttribute('ORG_ID'),
                            xUchrName);//Uchr.getAttribute('DESCRIPTION'));
  if (xNomTL = '') then begin
    // без записи - по согласованию с ДЭ
    // добавлено сообщение в журнал в функции GetExportNomTL,
    // поэтому тут завершаем выгрузку с результатом =-1, что приведет
    // к прерыванию выгрузки вообще.
    //raise ('Отменено пользователем.');
    Result := -1;
    EXIT;
  end;
  Data.setAttribute( 'NOM_TL', TRIM( xNomTL));
//  Data.setAttribute('NOM_TL', PacketNumber);

  Data.setAttribute('DATE_TL', FormatDateTime('dd.mm.yyyy', Work_Date));
  Budget := QueryXml(sqlBudgId, BudgetId);
  Budget := Budget.documentElement.Childnodes.item[0];
  Data.setAttribute('GOD_SUBS', Budget.getAttribute('FINYEAR'));
  if Data.getAttribute('GLAVA_UCHR') <> '' then
    Data.setAttribute('GLAVA_UCHR', StringReplace(Format('%3s', [Data.getAttribute('GLAVA_UCHR')]), ' ', '0'));
  Data.setAttribute('NAME_ORG', Ufk.getAttribute('DESCRIPTION'));
  Data.setAttribute('GLAVA_ORG', Ufk.getAttribute('KADM_CODE'));
  if Data.getAttribute('GLAVA_ORG') <> '' then
    Data.setAttribute('GLAVA_ORG', StringReplace(Format('%3s', [Data.getAttribute('GLAVA_ORG')]), ' ', '0'));
  Data.setAttribute('NAME_BUD', Budget.getAttribute('CAPTION'));
  // Exchange #2334
  Data.setAttribute('OKATO', QueryField(sqlTerritoryOKATO, [Budget.getAttribute('TERRITORY_ID')], 'TEROKATO'));

{
  sql := 'Select Rp.*, A.Caption from RespPerson Rp' +
    ' Left Join Appointment A on Rp.Appointment_Id = A.Id' +
    ' Where Rp.Org_Id = ? and A.Id = ?';
    }
  // exchange #2983
  Data.setAttribute('NAME_RUK', xNAME_RUK);//QueryField(sql, [Uchr.getAttribute('ORG_ID'), '-2'], 'FIO'));
  Data.setAttribute('NAME_RUK_FES', xNAME_RUK_FES);//QueryField(sql, [Uchr.getAttribute('ORG_ID'), '-4'], 'FIO'));

  // exchange #2983
  Data.setAttribute('DOL_ISP_1', xDOL_ISP_1);
  Data.setAttribute('FIO_ISP_1', xFIO_ISP_1);
  Data.setAttribute('TEL_ISP_1', xTEL_ISP_1);
{
  Xml := QueryXml(sql, [Uchr.getAttribute('ORG_ID'), '-5']);
  if Xml.documentElement.childNodes.Length = 1 then
  begin
    Xml := Xml.documentElement.childNodes.Item[0];
    // exchange #2901
    // нужно чтобы выгружался не тип должности из справ.должностей,
    // а наименование из ответственного исполнителя.
    // поэтому вместо A.CAPTION будем выгружать Pr.APPOINTMENT_NAME
    Data.setAttribute('DOL_ISP_1', Xml.getAttribute('APPOINTMENT_NAME'));
    Data.setAttribute('FIO_ISP_1', Xml.getAttribute('FIO'));
    Data.setAttribute('TEL_ISP_1', Xml.getAttribute('PHONE'));
  end;}

  // дата подписания
  Data.setAttribute('DATE_POD_1', FormatDateTime('dd.mm.yyyy', Work_Date));
  Data := Data.parentNode;
  WriteLog('Получение данных по кодам субсидии...', True);
  // Exchange #2334
  // добавлен фильтр: по списку справочника субсидийи инвестиций.
  // в результате выбирать записи нужно только из этого справочника.
  // exchange #2807
  // добавилось поле "код ФАИП" для выгрузки.
  // совместимость с АЦК-Ф не ниже 2.34.0.177
  // exchange #3081
  // учитывается когф.параметр - выгрузка по 90н (без косгу)
  Kbk := Bk_GrantInvestment( BudgetId,
                             xUchrID,//Uchr.getAttribute('ORG_ID'),
                             StatusBar,
                             ProgressBar,
                             vFilterGrandInvesment,
                             Work_Date,
                             (NOT vCfg_3049));
  if VarIsNull( Kbk) then begin
    // без записи - добавлено сообщение в лог
    WriteInfo('Отмена выгрузки.', True);
    // ошибка выгрузки
    Result := -1;
    EXIT;
  end;
{
//xFilter:= TStringList.Create;
//Kbk.save('C:\Temp\GrantInvestmentList.xml');
//xKBK_List := Kbk.childNodes.Item[0];
//showmessage(Kbk.xml);
xKBK_List := ChoiceGrantInvestmentList( Uchr.getAttribute('ORG_ID'), DirIni, Kbk.childNodes.Item[0], nil);//xFilter);
if VarIsNull(xKBK_List) then
  raise('Отменено пользователем.');//exit;
Kbk.LoadXml( xKBK_List.xml);
showmessage(Kbk.xml);
exit;
}
  Kbk := Kbk.documentElement;
  ProgressBar.Max := Kbk.ChildNodes.Length;
  WriteLog('Формирование данных по субсидиям к выгрузке...', True);

  for i := 0 to Kbk.ChildNodes.Length - 1 do
  begin
    ProgressBar.Position := i + 1 ;
    Kbk := Kbk.ChildNodes.Item[i];
    if (param_VariantUnloadKBK = 'только измененные строки') or
      (param_VariantUnloadKBK = 'все последние строки') then
    begin
      // exchange #3027
      // если выгрузка из списка, игнорируем проверку в кеше
      if param_OutListKbk
        then xCodeOut := TRUE
        else begin
          //ищем выбранный код в sysparam
          NodeXmlList := OldKbk.SelectNodes('//ORGID_' + xUchrID{Uchr.getAttribute('ORG_ID')} + '/ROW[@CODE=''' +
            Kbk.getAttribute('CODE') + ''' and @CNAME =''' + Trim(Kbk.getAttribute('CAPTION')) + ''']');
          xCodeOut := (NodeXmlList.length = 0);
        end;

      if xCodeOut then
      begin
        // без записи - по согласованию с ДЭ
        // добавлено сообщение в лог
        WriteLog( 'Выгружен код: ' + Kbk.getAttribute('CODE'));

        // exchange #3027
        // сохраняем ид.выгруженного кода
        aListID.Add( Kbk.getAttribute('ID'));

        Data := Data.appendChild(FillElementBySchema(Kbk, 'TLIS'));
        Data.setAttribute('DATE_NPA', IfThen(Data.getAttribute('DATE_NPA') <> '',
          FormatDateTime('dd.mm.yyyy', XmlToDateTime(Data.getAttribute('DATE_NPA'))), ''));

      // exchange #2629
      Data.setAttribute( 'NAME_SUBS',
                         ExchangeQuoteChars2629(
                                        'TL',
                                        Data.getAttribute('NAME_SUBS')));

        // exchange #2807
        // выгрузка поля ФАИП code
        Data.SetAttribute( 'FAIP_CODE', Kbk.GetAttribute('FAIP_CODE'));

        Data := Data.parentNode;
      end else begin
        // без записи - по согласованию с ДЭ
        // добавлено сообщение в лог
        WriteInfo( 'Код ' + Kbk.getAttribute('CODE') + ' - не выгружен');
      end;
    end
    //все строки
    else begin
      // без записи - по согласованию с ДЭ
      // добавлено сообщение в лог
      WriteLog( 'Выгружен код: ' + Kbk.getAttribute('CODE'));

      // exchange #3027
      // сохраняем ид.выгруженного кода
      aListID.Add( Kbk.getAttribute('ID'));

    Data := Data.appendChild(FillElementBySchema(Kbk, 'TLIS'));
    Data.setAttribute('DATE_NPA', IfThen(Data.getAttribute('DATE_NPA') <> '',
        FormatDateTime('dd.mm.yyyy', XmlToDateTime(Data.getAttribute('DATE_NPA'))), ''));

      // exchange #2629
      Data.setAttribute( 'NAME_SUBS',
                         ExchangeQuoteChars2629(
                                        'TL',
                                        Data.getAttribute('NAME_SUBS')));

        // exchange #2807
        // выгрузка поля ФАИП code
        Data.SetAttribute( 'FAIP_CODE', Kbk.GetAttribute('FAIP_CODE'));

    Data := Data.parentNode;
    end;
    //записываем строку истории кода в sysparam, даже если она не выгружалась
    CurrHistory := CurrHistory.appendChild(CurrHistory.ownerDocument.createElement('ROW'));
    CurrHistory.setAttribute('CNAME', Trim(Kbk.getAttribute('CAPTION')));
    CurrHistory.setAttribute('CODE', Trim(Kbk.getAttribute('CODE')));
    CurrHistory := CurrHistory.parentNode;
    Kbk := Kbk.parentNode;
  end;

  if (Data.SelectNodes('TLIS').length > 0)
    then Result := 1
    // без записи - по согласованию с ДЭ
    //  добавлено сообщение в лог
    else WriteInfo( 'Не выгружено ни одного кода субсидии.');
  Data := Data.ownerDocument;
      except
        ExceptionProc( 'MakeFormatGrantInvestment');
    end;

end;

//сделано под текущию реализацию выборки КБК из базы
//NodeName может быть только BEGIN_DATE или CHANGE_DATE
//Возвращает максимальную дату среди дат в Node c атрибутами BEGIN_DATE,
//BEGIN_DATE1 ... или CHANGE_DATE, CHANGE_DATE1... в зависимости от параметра
//NodeName. NodeName подается в виде Xml даты. Пустых данных быть не может
function MaxDateFromNode(Node: Variant; NodeName: String): String;
var
  j: Integer;
begin
  Result := FormatDateTime('dd.mm.yyyy hh:nn:ss',
    XmlToDateTime(Node.getAttribute(NodeName)));
  for j := 1 to 4 do
    if (not VarIsNull(Node.getAttribute(NodeName + IntToStr(j)))) then
    begin
      if (StrToDateTime(Result) < XmlToDateTime(Node.getAttribute(NodeName +
        IntToStr(j)))) then
        Result := FormatDateTime('dd.mm.yyyy hh:nn:ss',
          XmlToDateTime(Node.getAttribute(NodeName + IntToStr(j))));
    end
    else
      Exit;
end;
//Поиск минимальной даты среди всех кодов. Для каждого кода ищется отрезок с
//с максимальной датой начала, среди этих отрезков выбирается отрезок с
//минимальной датой окончания
function MinDateFromNodes(Nodes: Variant): String;
var
  i, j, indxNode: Integer;
  Begin_Date: TDate;
begin
  Result := '';
  indxNode := 0;
  Begin_Date := XmlToDateTime(Nodes.ChildNodes.Item[0].getAttribute('BEGIN_DATE'));
  //Для первого кода цикл по нодам отдельный
  for i := 0 to Nodes.ChildNodes.Length - 1 do
  begin
    Nodes := Nodes.ChildNodes.Item[i];
    if (Begin_Date < XmlToDateTime(Nodes.getAttribute('BEGIN_DATE'))) then
    begin
      Begin_Date := XmlToDateTime(Nodes.getAttribute('BEGIN_DATE'));
      indxNode := i;
    end;
    Nodes := Nodes.parentNode;
  end;
  Result := IfThen(Nodes.ChildNodes.Item[indxNode].getAttribute('END_DATE') <> '',
    FormatDateTime('dd.mm.yyyy',
      XmlToDateTime(Nodes.ChildNodes.Item[indxNode].getAttribute('END_DATE'))), '');
  //цикл по атрибутам
  for j := 1 to 4 do
  begin
    if (not VarIsNull(Nodes.ChildNodes.Item[0].getAttribute('BEGIN_DATE' + IntToStr(j)))) then
    begin
      Begin_Date := XmlToDateTime(Nodes.ChildNodes.Item[0].getAttribute('BEGIN_DATE' +
        IntToStr(j)));
      for i := 0 to Nodes.ChildNodes.Length - 1 do
      begin
        Nodes := Nodes.ChildNodes.Item[i];
        if (Begin_Date < XmlToDateTime(Nodes.getAttribute('BEGIN_DATE' + IntToStr(j)))) then
        begin
          Begin_Date := XmlToDateTime(Nodes.getAttribute('BEGIN_DATE' + IntToStr(j)));
          indxNode := i;
        end;
        Nodes := Nodes.parentNode;
      end;
      if (Result <> '') and (Nodes.ChildNodes.Item[indxNode].getAttribute('END_DATE' + IntToStr(j)) <> '') then
      begin
        if (StrToDate(Result) > XmlToDateTime(Nodes.ChildNodes.Item[indxNode].getAttribute('END_DATE' +
          IntToStr(j)))) then
          Result := FormatDateTime('dd.mm.yyyy',
            XmlToDateTime(Nodes.ChildNodes.Item[indxNode].getAttribute('END_DATE' + IntToStr(j))));
      end
      else if (Nodes.ChildNodes.Item[indxNode].getAttribute('END_DATE' + IntToStr(j)) <> '') then
        Result := FormatDateTime('dd.mm.yyyy',
          XmlToDateTime(Nodes.ChildNodes.Item[indxNode].getAttribute('END_DATE' + IntToStr(j))));
    end
    else Exit;
  end;
end;

//Возвращает строку для записи в файл, в зависимости от параметра
//param_VariantUnloadKBK
//CodeLines - содержит код из РЧБ со всеми строками
//XmlListCodeforCds - используется для выгрузки списка кодов. Для формирования в cds
procedure KbkLineRash(CodeLines, XmlListCodeforCds: Variant;
  param_OutListKbk: Boolean);
var
  Begin_Date, End_Date, Change_Date, NewBegin_Date, NewEnd_Date,
  NewChange_Date, Kvsr_Name, Kfsr_Name, Kcsr_Name, Kvr_Name, Kes_Name: String;
  i: Integer;
  Kvsr_Date, Kfsr_Date, Kcsr_Date, Kvr_Date, Kes_Date: TDate;
  Lim: Boolean;// флаг выгрузки доведения лимитов
begin
  //для выгрузки всех кодов, независимо от параметра
  //один алгоритм формирования данных
  Kcsr_Name := CodeLines.ChildNodes.Item[0].getAttribute('KCSR_DESC');
  Kvr_Name := CodeLines.ChildNodes.Item[0].getAttribute('KVR_DESC');
  Kfsr_Name := CodeLines.ChildNodes.Item[0].getAttribute('KFSR_DESC');
  Kes_Name := CodeLines.ChildNodes.Item[0].getAttribute('KESR_DESC');
  Kvsr_Name := CodeLines.ChildNodes.Item[0].getAttribute('KADMR_DESC');
  Kcsr_Date := XmlToDateTime(CodeLines.ChildNodes.Item[0].getAttribute('BEGIN_DATE'));
  Kvr_Date := XmlToDateTime(CodeLines.ChildNodes.Item[0].getAttribute('BEGIN_DATE1'));
  Kfsr_Date := XmlToDateTime(CodeLines.ChildNodes.Item[0].getAttribute('BEGIN_DATE2'));
  Kes_Date := XmlToDateTime(CodeLines.ChildNodes.Item[0].getAttribute('BEGIN_DATE3'));
  Kvsr_Date := XmlToDateTime(CodeLines.ChildNodes.Item[0].getAttribute('BEGIN_DATE4'));
  //выбираем максимальную дату начала и минимальную дату окончания действия
  Begin_Date := MaxDateFromNode(CodeLines.ChildNodes.Item[0], 'BEGIN_DATE');
  Change_Date := MaxDateFromNode(CodeLines.ChildNodes.Item[0], 'CHANGE_DATE');
  Lim := True;
  for i := 1 to CodeLines.ChildNodes.Length - 1 do
  begin
    CodeLines := CodeLines.ChildNodes.Item[i];
    NewBegin_Date := MaxDateFromNode(CodeLines, 'BEGIN_DATE');
    NewChange_Date := MaxDateFromNode(CodeLines, 'CHANGE_DATE');
    if StrToDateTime(NewBegin_Date) > StrToDateTime(Begin_Date) then
      Begin_Date := NewBegin_Date;
    if StrToDateTime(NewChange_Date) > StrToDateTime(Change_Date) then
      Change_Date := NewChange_Date;
    if (Kcsr_Date < XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE'))) then
    begin
      Kcsr_Date := XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE'));
      Kcsr_Name := CodeLines.getAttribute('KCSR_DESC');
    end;
    if (Kvr_Date < XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE1'))) then
    begin
      Kvr_Date := XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE1'));
      Kvr_Name := CodeLines.getAttribute('KVR_DESC');
    end;
    if not param_NameKBK then begin
      if (Kfsr_Date < XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE2'))) then
      begin
        Kfsr_Date := XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE2'));
        Kfsr_Name := CodeLines.getAttribute('KFSR_DESC');
      end;
      if (Kes_Date < XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE3'))) then
      begin
        Kes_Date := XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE3'));
        Kes_Name := CodeLines.getAttribute('KESR_DESC');
      end;
      if (Kvsr_Date < XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE4'))) then
      begin
        Kvsr_Date := XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE4'));
        Kvsr_Name := CodeLines.getAttribute('KADMR_DESC');
      end;
    end;
    //Exchange #2167
    if (Lim) and (CodeLines.getAttribute('LIM') = 0) then
    begin
      Lim := False;
      CodeLines.parentNode.ChildNodes.Item[0].setAttribute('LIM', 0);
    end;
    CodeLines := CodeLines.parentNode;
  end;
  End_Date := MinDateFromNodes(CodeLines);
  CodeLines := CodeLines.childNodes.Item[0];
  //для выгрузки КБК из списка нужны только коды с правильными датами
  if param_OutListKbk then
  begin
    //проверка такая же как и при параметре все строки. По старой постановке.
    if (End_Date = '') or (StrToDateTime(Begin_Date) < StrToDateTime(End_Date)) then
    begin
      XmlListCodeforCds :=
        XmlListCodeforCds.appendChild(XmlListCodeforCds.ownerDocument.createElement('ROW'));
      XmlListCodeforCds.setAttribute('CODE', CodeLines.getAttribute('CODE'));
      if param_NameKBK then
        XmlListCodeforCds.setAttribute('DESCRIPTION',
          Kcsr_Name + Ifthen((Kcsr_Name <> '') and (Kvr_Name <> ''), ' ') + Kvr_Name)
      else
        XmlListCodeforCds.setAttribute('DESCRIPTION', Kvsr_Name + '.' +
          Kfsr_Name + '.' + Kcsr_Name + '.' + Kvr_Name + '.' + Kes_Name);
      XmlListCodeforCds.setAttribute('BEGIN_DATE', DateTimeToXml(StrToDateTime(Begin_Date)));
      XmlListCodeforCds.setAttribute('CHANGE_DATE', DateTimeToXml(StrToDateTime(Change_Date)));
      if (End_Date <> '') then
        XmlListCodeforCds.setAttribute('END_DATE', DateTimeToXml(StrToDateTime(End_Date)))
      else
        XmlListCodeforCds.setAttribute('END_DATE', '');
      XmlListCodeforCds.setAttribute('TYPE_KBK', 10);
      XmlListCodeforCds.setAttribute('LIMIT', CodeLines.getAttribute('LIM'));
      XmlListCodeforCds.setAttribute('CAPTION', '');
      XmlListCodeforCds := XmlListCodeforCds.parentNode;
    end;
    Exit;
  end;
  if param_VariantUnloadKBK = 'только измененные строки' then
  begin
    //ищем выбранный код в sysparam
    if param_NameKBK then
      NodeXmlList := OldKbk.SelectNodes('//ROW[@CODE=''' + CodeLines.getAttribute('CODE') +
        ''' and @NAME=''' + Trim(Kcsr_Name + ' ' + Kvr_Name) + ''' and @DATE_BEGIN=''' +
        DateToStr(StrToDateTime(Begin_Date)) + ''' and @DATE_END=''' + DateToStr(StrToDateTime(End_Date)) + ''']')
    else
      NodeXmlList := OldKbk.SelectNodes('//ROW[@CODE=''' + CodeLines.getAttribute('CODE') +
        ''' and @NAME=''' + Trim(Kvsr_Name + '.' + Kfsr_Name + '.' + Kcsr_Name + '.'
        + Kvr_Name + '.' + Kes_Name) + ''' and @DATE_BEGIN=''' +
        DateToStr(StrToDateTime(Begin_Date)) + ''' and @DATE_END=''' + DateToStr(StrToDateTime(End_Date)) + ''']');
    if NodeXmlList.length = 0 then
    begin
      Data := Data.appendChild(FillElementBySchema(CodeLines, 'DKST'));
      Data.setAttribute('TYPE_KBK', 10);
      if param_NameKBK then
        Data.setAttribute('NAME', Kcsr_Name + Ifthen((Kcsr_Name <> '') and (Kvr_Name <> ''), ' ') + Kvr_Name)
      else
        Data.setAttribute('NAME', Kvsr_Name + '.' + Kfsr_Name + '.' + Kcsr_Name + '.'
          + Kvr_Name + '.' + Kes_Name);
      Data.setAttribute('LIMIT', CodeLines.getAttribute('LIM'));
      Data.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
      Data.setAttribute('DATE_CHANGE', Change_Date);
      Data.setAttribute('DATE_END', End_Date);
      Data := Data.parentNode;
    end;
  end
  else if param_VariantUnloadKBK = 'все строки' then
  begin
    if (End_Date = '') or (StrToDateTime(Begin_Date) < StrToDateTime(End_Date)) then
    begin
      Data := Data.appendChild(FillElementBySchema(CodeLines, 'DKST'));
      Data.setAttribute('TYPE_KBK', 10);
      if param_NameKBK then
        Data.setAttribute('NAME', Kcsr_Name + Ifthen((Kcsr_Name <> '') and (Kvr_Name <> ''), ' ') + Kvr_Name)
      else
        Data.setAttribute('NAME', Kvsr_Name + '.' + Kfsr_Name + '.' + Kcsr_Name + '.'
          + Kvr_Name + '.' + Kes_Name);
      Data.setAttribute('LIMIT', CodeLines.getAttribute('LIM'));
      Data.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
      Data.setAttribute('DATE_CHANGE', Change_Date);
      Data.setAttribute('DATE_END', End_Date);
      Data := Data.parentNode;
    end;
  end
  //все последние строки
  else begin
    if (End_Date = '') or ((StrToDateTime(Begin_Date) < StrToDateTime(End_Date))
      and (Work_Date <= StrToDate(End_Date))) then
    begin
      Data := Data.appendChild(FillElementBySchema(CodeLines, 'DKST'));
      Data.setAttribute('TYPE_KBK', 10);
      if param_NameKBK then
        Data.setAttribute('NAME', Kcsr_Name + Ifthen((Kcsr_Name <> '') and (Kvr_Name <> ''), ' ') + Kvr_Name)
      else
        Data.setAttribute('NAME', Kvsr_Name + '.' + Kfsr_Name + '.' + Kcsr_Name + '.'
          + Kvr_Name + '.' + Kes_Name);
      Data.setAttribute('LIMIT', CodeLines.getAttribute('LIM'));
      Data.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
      Data.setAttribute('DATE_CHANGE', Change_Date);
      Data.setAttribute('DATE_END', End_Date);
      Data := Data.parentNode;
    end;
  end;
  //историю по коду перезаписываем в любом случае. постановка.
  CurrHistory := CurrHistory.appendChild(CurrHistory.ownerDocument.createElement('ROW'));
  CurrHistory.setAttribute('CODE', CodeLines.getAttribute('CODE'));
  CurrHistory.setAttribute('CNAME', '');
  if param_NameKBK then
    CurrHistory.setAttribute('NAME', Trim(Kcsr_Name + ' ' + Kvr_Name))
  else
    CurrHistory.setAttribute('NAME', Trim(Kvsr_Name + '.' + Kfsr_Name + '.' + Kcsr_Name + '.'
      + Kvr_Name + '.' + Kes_Name));
  CurrHistory.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
  CurrHistory.setAttribute('DATE_END', End_Date);
  CurrHistory := CurrHistory.parentNode;
  CodeLines := CodeLines.parentNode;
end;

//Возвращает строку для записи в файл, в зависимости от параметра
//param_VariantUnloadKBK
//CodeLines - содержит код из ДЧБ со всеми строками
//XmlListCodeforCds - используется для выгрузки списка кодов. Для формирования в cds
procedure KbkLineDoh(CodeLines, XmlListCodeforCds: Variant;
  param_OutListKbk: Boolean);
var
  Begin_Date, End_Date, Change_Date, NewBegin_Date, NewEnd_Date,
  NewChange_Date, Description: String;
  i: Integer;
  Kd_Date: TDate;
begin
  //для выгрузки всех кодов, независимо от параметра
  //один алгоритм формирования данных
  Description := CodeLines.ChildNodes.Item[0].getAttribute('DESCRIPTION');
  Kd_Date := XmlToDateTime(CodeLines.ChildNodes.Item[0].getAttribute('BEGIN_DATE'));
  //выбираем максимальную дату начала и минимальную дату окончания действия
  Begin_Date := MaxDateFromNode(CodeLines.ChildNodes.Item[0], 'BEGIN_DATE');
  Change_Date := MaxDateFromNode(CodeLines.ChildNodes.Item[0], 'CHANGE_DATE');
  for i := 1 to CodeLines.ChildNodes.Length - 1 do
  begin
    CodeLines := CodeLines.ChildNodes.Item[i];
    NewBegin_Date := MaxDateFromNode(CodeLines, 'BEGIN_DATE');
    NewChange_Date := MaxDateFromNode(CodeLines, 'CHANGE_DATE');
    if StrToDateTime(NewBegin_Date) > StrToDateTime(Begin_Date) then
      Begin_Date := NewBegin_Date;
    if StrToDateTime(NewChange_Date) > StrToDateTime(Change_Date) then
      Change_Date := NewChange_Date;
    if (Kd_Date < XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE'))) then
    begin
      Kd_Date := XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE'));
      Description := CodeLines.getAttribute('DESCRIPTION');
    end;
    CodeLines := CodeLines.parentNode;
  end;
  End_Date := MinDateFromNodes(CodeLines);
  CodeLines := CodeLines.childNodes.Item[0];
  if param_OutListKbk then
  begin
    //проверка такая же как и при параметре все строки. По старой постановке.
    if (End_Date = '') or (StrToDateTime(Begin_Date) < StrToDateTime(End_Date)) then
    begin
      XmlListCodeforCds :=
        XmlListCodeforCds.appendChild(XmlListCodeforCds.ownerDocument.createElement('ROW'));
      XmlListCodeforCds.setAttribute('CODE', CodeLines.getAttribute('CODE'));
      XmlListCodeforCds.setAttribute('DESCRIPTION', Description);
      XmlListCodeforCds.setAttribute('BEGIN_DATE', DateTimeToXml(StrToDateTime(Begin_Date)));
      XmlListCodeforCds.setAttribute('CHANGE_DATE', DateTimeToXml(StrToDateTime(Change_Date)));
      if (End_Date <> '') then
        XmlListCodeforCds.setAttribute('END_DATE', DateTimeToXml(StrToDateTime(End_Date)))
      else
        XmlListCodeforCds.setAttribute('END_DATE', '');
      XmlListCodeforCds.setAttribute('TYPE_KBK', 20);
      XmlListCodeforCds.setAttribute('CAPTION', '');
      XmlListCodeforCds := XmlListCodeforCds.parentNode;
    end;
    Exit;
  end;
  if param_VariantUnloadKBK = 'только измененные строки' then
  begin
    //ищем выбранный код в sysparam
    NodeXmlList := OldKbk.SelectNodes('//ROW[@CODE=''' + CodeLines.getAttribute('CODE') +
      ''' and @NAME=''' + Trim(Description) + ''' and @DATE_BEGIN=''' +
      DateToStr(StrToDateTime(Begin_Date)) + ''' and @DATE_END=''' + DateToStr(StrToDateTime(End_Date)) + ''']');
    if NodeXmlList.length = 0 then
    begin
      Data := Data.appendChild(FillElementBySchema(CodeLines, 'DKST'));
      Data.setAttribute('TYPE_KBK', 20);
      Data.setAttribute('NAME', Description);
      Data.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
      Data.setAttribute('DATE_CHANGE', Change_Date);
      Data.setAttribute('DATE_END', End_Date);
      Data := Data.parentNode;
    end;
  end
  else if param_VariantUnloadKBK = 'все строки' then
  begin
    if (End_Date = '') or (StrToDateTime(Begin_Date) < StrToDateTime(End_Date)) then
    begin
      Data := Data.appendChild(FillElementBySchema(CodeLines, 'DKST'));
      Data.setAttribute('TYPE_KBK', 20);
      Data.setAttribute('NAME', Description);
      Data.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
      Data.setAttribute('DATE_CHANGE', Change_Date);
      Data.setAttribute('DATE_END', End_Date);
      Data := Data.parentNode;
    end;
  end
  //все последние строки
  else begin
    if (End_Date = '') or ((StrToDateTime(Begin_Date) < StrToDateTime(End_Date))
      and (Work_Date <= StrToDate(End_Date))) then
    begin
      Data := Data.appendChild(FillElementBySchema(CodeLines, 'DKST'));
      Data.setAttribute('TYPE_KBK', 20);
      Data.setAttribute('NAME', Description);
      Data.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
      Data.setAttribute('DATE_CHANGE', Change_Date);
      Data.setAttribute('DATE_END', End_Date);
      Data := Data.parentNode;
    end;
  end;
  //историю по коду перезаписываем в любом случае. постановка.
  CurrHistory := CurrHistory.appendChild(CurrHistory.ownerDocument.createElement('ROW'));
  CurrHistory.setAttribute('CODE', CodeLines.getAttribute('CODE'));
  CurrHistory.setAttribute('CNAME', '');
  CurrHistory.setAttribute('NAME', Trim(Description));
  CurrHistory.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
  CurrHistory.setAttribute('DATE_END', End_Date);
  CurrHistory := CurrHistory.parentNode;
  CodeLines := CodeLines.parentNode;
end;

//Возвращает строку для записи в файл, в зависимости от параметра
//param_VariantUnloadKBK
//CodeLines - содержит код из ДЧБ со всеми строками
//XmlListCodeforCds - используется для выгрузки списка кодов. Для формирования в cds
procedure KbkLineIst(CodeLines, XmlListCodeforCds: Variant;
  param_OutListKbk: Boolean);
var
  Begin_Date, End_Date, Change_Date, NewBegin_Date, NewEnd_Date,
  NewChange_Date, Description: String;
  i: Integer;
  Ki_Date: TDate;
begin
  //для выгрузки всех кодов, независимо от параметра
  //один алгоритм формирования данных
  Description := CodeLines.childNodes.Item[0].getAttribute('DESCRIPTION');
  Ki_Date := XmlToDateTime(CodeLines.childNodes.Item[0].getAttribute('BEGIN_DATE'));
  //выбираем максимальную дату начала и минимальную дату окончания действия
  Begin_Date := MaxDateFromNode(CodeLines.childNodes.Item[0], 'BEGIN_DATE');
  Change_Date := MaxDateFromNode(CodeLines.childNodes.Item[0], 'CHANGE_DATE');
  for i := 1 to CodeLines.childNodes.Length - 1 do
  begin
    CodeLines := CodeLines.childNodes.Item[i];
    NewBegin_Date := MaxDateFromNode(CodeLines, 'BEGIN_DATE');
    NewChange_Date := MaxDateFromNode(CodeLines, 'CHANGE_DATE');
    if StrToDateTime(NewBegin_Date) > StrToDateTime(Begin_Date) then
      Begin_Date := NewBegin_Date;
    if StrToDateTime(NewChange_Date) > StrToDateTime(Change_Date) then
      Change_Date := NewChange_Date;
    if (Ki_Date < XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE'))) then
    begin
      Ki_Date := XmlToDateTime(CodeLines.getAttribute('BEGIN_DATE'));
      Description := CodeLines.getAttribute('DESCRIPTION');
    end;
    CodeLines := CodeLines.parentNode;
  end;
  End_Date := MinDateFromNodes(CodeLines);
  CodeLines := CodeLines.childNodes.Item[0];
  if param_OutListKbk then
  begin
    //проверка такая же как и при параметре все строки. По старой постановке.
    if (End_Date = '') or (StrToDateTime(Begin_Date) < StrToDateTime(End_Date)) then
    begin
      XmlListCodeforCds :=
        XmlListCodeforCds.appendChild(XmlListCodeforCds.ownerDocument.createElement('ROW'));
      XmlListCodeforCds.setAttribute('CODE', CodeLines.getAttribute('CODE'));
      XmlListCodeforCds.setAttribute('DESCRIPTION', Description);
      XmlListCodeforCds.setAttribute('BEGIN_DATE', DateTimeToXml(StrToDateTime(Begin_Date)));
      XmlListCodeforCds.setAttribute('CHANGE_DATE', DateTimeToXml(StrToDateTime(Change_Date)));
      if (End_Date <> '') then
        XmlListCodeforCds.setAttribute('END_DATE', DateTimeToXml(StrToDateTime(End_Date)))
      else
        XmlListCodeforCds.setAttribute('END_DATE', '');
      if Copy(KiCodeFormat(CodeLines.getAttribute('KI_CODE'), BudgetId), 1, 2) = '01' then
        XmlListCodeforCds.setAttribute('TYPE_KBK', 31)
      else
        XmlListCodeforCds.setAttribute('TYPE_KBK', 32);
      XmlListCodeforCds.setAttribute('CAPTION', '');
      XmlListCodeforCds := XmlListCodeforCds.parentNode;
    end;
    Exit;
  end;
  if param_VariantUnloadKBK = 'только измененные строки' then
  begin
    //ищем выбранный код в sysparam
    NodeXmlList := OldKbk.SelectNodes('//ROW[@CODE=''' + CodeLines.getAttribute('CODE') +
      ''' and @NAME=''' + Description + ''' and @DATE_BEGIN=''' +
      DateToStr(StrToDateTime(Begin_Date)) + ''' and @DATE_END=''' + DateToStr(StrToDateTime(End_Date)) + ''']');
    if NodeXmlList.length = 0 then
    begin
      Data := Data.appendChild(FillElementBySchema(CodeLines, 'DKST'));
      if Copy(KiCodeFormat(CodeLines.getAttribute('KI_CODE'), BudgetId), 1, 2) = '01' then
        Data.setAttribute('TYPE_KBK', 31)
      else
        Data.setAttribute('TYPE_KBK', 32);
      Data.setAttribute('NAME', Description);
      Data.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
      Data.setAttribute('DATE_CHANGE', Change_Date);
      Data.setAttribute('DATE_END', End_Date);
      Data := Data.parentNode;
    end;
  end
  else if param_VariantUnloadKBK = 'все строки' then
  begin
    if (End_Date = '') or (StrToDateTime(Begin_Date) < StrToDateTime(End_Date)) then
    begin
      Data := Data.appendChild(FillElementBySchema(CodeLines, 'DKST'));
      if Copy(KiCodeFormat(CodeLines.getAttribute('KI_CODE'), BudgetId), 1, 2) = '01' then
        Data.setAttribute('TYPE_KBK', 31)
      else
        Data.setAttribute('TYPE_KBK', 32);
      Data.setAttribute('NAME', Description);
      Data.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
      Data.setAttribute('DATE_CHANGE', Change_Date);
      Data.setAttribute('DATE_END', End_Date);
      Data := Data.parentNode;
    end;
  end
  //все последние строки
  else begin
    if (End_Date = '') or ((StrToDateTime(Begin_Date) < StrToDateTime(End_Date))
      and (Work_Date <= StrToDate(End_Date))) then
    begin
      Data := Data.appendChild(FillElementBySchema(CodeLines, 'DKST'));
      if Copy(KiCodeFormat(CodeLines.getAttribute('KI_CODE'), BudgetId), 1, 2) = '01' then
        Data.setAttribute('TYPE_KBK', 31)
      else
        Data.setAttribute('TYPE_KBK', 32);
      Data.setAttribute('NAME', Description);
      Data.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
      Data.setAttribute('DATE_CHANGE', Change_Date);
      Data.setAttribute('DATE_END', End_Date);
      Data := Data.parentNode;
    end;
  end;
  //историю по коду перезаписываем в любом случае. постановка.
  CurrHistory := CurrHistory.appendChild(CurrHistory.ownerDocument.createElement('ROW'));
  CurrHistory.setAttribute('CODE', CodeLines.getAttribute('CODE'));
  CurrHistory.setAttribute('CNAME', '');
  CurrHistory.setAttribute('NAME', Description);
  CurrHistory.setAttribute('DATE_BEGIN', DateToStr(StrToDateTime(Begin_Date)));
  CurrHistory.setAttribute('DATE_END', End_Date);
  CurrHistory := CurrHistory.parentNode;
  CodeLines := CodeLines.parentNode;
end;

procedure SaveDataInSysParam;
begin

  //историю выгрузки не сохраняем при ручном выборе КБК
  if param_OutListKbk then Exit;
  //Запись выгруженных КБК.
  WriteLog('Сохранение данных по выгруженным КБК...');
  OldKbk := OldKbk.ownerDocument;
  SysParam := CreateOleObject('Microsoft.XmlDom');
  with SysParam.appendChild(SysParam.createElement('REFERENCE')) do
  begin
    setAttribute('ref_name', 'SYSPARAM');
    setAttribute('action', 'save');
    with appendChild(SysParam.createElement('SYSPARAM')) do
    begin
      setAttribute('NAME', 'exchange.kbkcompare');
      with appendChild(SysParam.createElement('PARAM_VALUE')) do
        with appendChild(SysParam.createCDATASection('')) do
          SysParam.getElementsByTagName('PARAM_VALUE').items[0].
            firstChild.Data := OldKbk.Xml;
    end;
    ExecXml(SysParam.xml);
  end;
  //OldKbk.Save('D:\AzkExchange\Sysparam.xml');
end;

function Initialize(Xml: Variant): boolean;
var
  SL: TStringList;
begin
  Result := TRUE;

  SL := TStringList.Create;
  with SL do
  try
    CommaText := InitializeDefault(Xml, ChangeFileExt(TestFileName, ''));
    //визиуализация выгрузки
    Form := TFormCustom.Create(Application);
    //LoadForm(DirIni + 'Forms\Fm_Process.dfm', Form);
    Form.LoadDfm( DirIni + 'Forms\Fm_Process.dfm');
    // связываем контролы прогресс бара и стутс бара с доп.формой
    AttachProgressControl(
            TProgressBar(Form.FindComponent('ProgressBar')),
            TStatusBar(Form.FindComponent('StatusBar')));
    //инициализация кэша КБК по маске
    CacheFormatKbk := TStringList.Create;
    DolIsp := Values['Должность исполнителя'];
    FioIsp := Values['ФИО исполнителя'];
    TelIsp := Values['Телефон исполнителя'];

    // exchange #2983
    // проверяем заполнение полей исполнителя.
    // нам нужно зафиксировать что все поля пустые. тогда значения будем
    // брать из бд.
    // переделано по постановке - для TL, если хотя бы одно поле
    // исполнителя в конфигурации не заполнено, то выгрузка полей будет
    // выполняться по бд.
    if (    ( LENGTH( TRIM( DolIsp)) <= 0)
        OR  ( LENGTH( TRIM( FioIsp)) <= 0)
        OR  ( LENGTH( TRIM( TelIsp)) <= 0))
      then vIspGetFromDB_TL := TRUE
      else vIspGetFromDB_TL := FALSE;
    // для выгрузки прочих кодов, если хотя бы одно из полей заполнено
    // выгружается по конфигу все три поля, а из бд выгружается
    // описание исполнителя, если все три поля не заполнены.
    if (    ( LENGTH( TRIM( DolIsp)) <= 0)
        AND ( LENGTH( TRIM( FioIsp)) <= 0)
        AND ( LENGTH( TRIM( TelIsp)) <= 0))
      then vIspGetFromDB := TRUE
      else vIspGetFromDB := FALSE;



    UnloadKbk := Values['Выгружать КБК'] = 'Да';
    UnloadGlava := Values['Выгружать справочник кодов глав'] = 'Да';
    UnloadKcsr := Values['Выгружать справочник кодов по КЦСР'] = 'Да';
    UnloadKvr := Values['Выгружать справочник кодов по КВР'] = 'Да';
    UnloadSubDohIst := Values['Выгружать справочник коды видов источников,' +
      ' коды подвидов доходов'] = 'Да';
    UnloadGrantInvestment := Values['Выгружать справочник целевых субсидий'] = 'Да';

    // exchange #2473
    vUnloadPurposefulgrant := Values['Выгружать справочник целевых назначений'] = 'Да';

    param_ref := Values['Выгружать коды глав, КЦСР, КВР, коды подвидов доходов,' +
      ' видов источников из справочника'] = 'Да';
    param_UnloadKbkFromAllBudget := Values['Выгружать КБК из всех бюджетов'] = 'Да';
    param_VariantUnloadKBK := Values['Вариант выгрузки КБК'];
    // селектор выгрузки КБК: все, последние, измененные
    vCfgSelectorKBK := Values['Вариант выгрузки КБК'];
    param_NameKBK := Values['Выгрузка наименования кбк расходов'] = 'КЦСР КВР';
    param_ModeSubDohIst := Values['Режим выгрузки кодов видов источников,' +
      ' кодов подвидов доходов'];
    param_ChangePrevYear :=
      Values['Выгружать измененные КБК относительно прошлого года'] = 'Да';
    //Exchange #2144
    param_ShowFormParam :=
      Values['Выдавать запрос на выгрузку справочников КБК'] = 'Да';

    // exchange #2417
    if (LowerCase( Values['Выгружать DKST.LIMIT из поля Признак использования для лимитируемых расходов']) = 'да')
      then vCfg_KBK_DKST_LIMIT_Option := 2
      else vCfg_KBK_DKST_LIMIT_Option := 1;

    // exchange #2851
    vCfg_2851 := (LowerCase( Values['Формировать KD по всем кодам цели']) = 'да');

    // exchange #2596
    // параметр Выгружать только «Утвержден на уровне субъекта (муниципального образования)»»
    vCfg_2596 :=
      ( Values['Выгружать только "Утвержден на уровне субъекта (муниципального образования)"']
        = 'Да');

    // exchange #3049
    // exchange #3081 (exchange #3091)
    // параметр убран из конфига и принимается всегда =Да (без КОСГУ)
    vCfg_3049 := TRUE;//( Values['Выгружать по требованиям Приказа 90н'] = 'Да');

    // exchange #2820
    vCfg_OrgID_Uchr := TRIM( SL.Values[ 'org_id']);
    // проверяем наличие указанной организации
    if (LENGTH( vCfg_OrgID_Uchr) > 0) then
    begin
      if (QueryField( 'select count( * ) N from org where id=?',
                      [ vCfg_OrgID_Uchr],
                      'N') <> '1')
        then begin
          Result := FALSE;
          ShowMessage( 'Указанный в конфигурации "Учредитель" не найден в БД.');
          EXIT;
        end;
    end;
    
    // Exchange, #3217
    vCfg_Org_FK := TRIM( SL.Values[ 'fk_org_name_description']);
    vCfg_OrgID_FK := TRIM( SL.Values[ 'fk_org_id']);
    // проверяем наличие указанной организации
    if (LENGTH( vCfg_OrgID_FK) > 0) then
    begin
      if (QueryField( 'select count( * ) N from org where id=?',
                      [ vCfg_OrgID_FK],
                      'N') <> '1')
        then begin
          Result := FALSE;
          ShowMessage( 'Указанная в конфигурации организация для параметра "Формирование блока TO" не найдена в БД.');
          EXIT;
        end;
    end;
    
    // Exchange 2336: поддерживаемая версия
    ParseParametersVersionSupport( DirIni, SL);

    // exchange #2629
    Exch2629_Initialize( SL);

    if param_ShowFormParam then
    begin
      Fm_ParamUnloadKbk := TFormCustom.Create(Application);
      try
        LoadForm(DirIni + 'Forms\Fm_ParamUnloadKbk.dfm', Fm_ParamUnloadKbk);
        UnloadKbk_Chbx :=
          TCheckBox(Fm_ParamUnloadKbk.FindComponent('UnloadKbk_Chbx'));
        UnloadKcsr_Chbx :=
          TCheckBox(Fm_ParamUnloadKbk.FindComponent('UnloadKcsr_Chbx'));
        UnloadKvr_Chbx :=
          TCheckBox(Fm_ParamUnloadKbk.FindComponent('UnloadKvr_Chbx'));
        UnloadPodvid_Chbx :=
          TCheckBox(Fm_ParamUnloadKbk.FindComponent('UnloadPodvid_Chbx'));
        UnloadGlava_Chbx :=
          TCheckBox(Fm_ParamUnloadKbk.FindComponent('UnloadGlava_Chbx'));
        UnloadGrantInvestment_Chbx :=
          TCheckBox(Fm_ParamUnloadKbk.FindComponent('UnloadGrantInvestment_Chbx'));

        // exchange #2473
        vUnloadPurposefulgrant_Chbx :=
          TCheckBox(Fm_ParamUnloadKbk.FindComponent('UnloadPurposefulgrant_Chbx'));

        OkBtn := TBitBtn(Fm_ParamUnloadKbk.FindComponent('OkBtn'));
        CancelBtn := TBitBtn(Fm_ParamUnloadKbk.FindComponent('CancelBtn'));
        //инициализация параметров из конфига, в конфиг сохранять
        //выбранные значения не надо. см постановку Exchange #2144
        UnloadKbk_Chbx.Checked := UnloadKbk;
        UnloadKcsr_Chbx.Checked := UnloadKcsr;
        UnloadKvr_Chbx.Checked := UnloadKvr;
        UnloadPodvid_Chbx.Checked := UnloadSubDohIst;
        UnloadGlava_Chbx.Checked := UnloadGlava;
        UnloadGrantInvestment_Chbx.Checked := UnloadGrantInvestment;

        // exchange #2473
        vUnloadPurposefulgrant_Chbx.Checked := vUnloadPurposefulgrant;

        OkBtn.OnClick := @OkBtnOnClick;
        CancelBtn.OnClick := @CancelBtnOnClick;
        // fm_ModalResultOk - нажата кнопка "Выгрузить"
        fm_ModalResultOk := Fm_ParamUnloadKbk.ShowModal = MrOk;
      finally
        Fm_ParamUnloadKbk.Close;
        Fm_ParamUnloadKbk.Free;
      end;
    end;
    param_OutListKbk := Values['Выгружать КБК из списка'] = 'Да';
    vCfgOutListKbk := Values['Выгружать КБК из списка'] = 'Да';

    if param_OutListKbk then
    begin
      Fm_KbkList := TFormCustomList.Create(Application);
      LoadForm(DirIni + 'Forms\Fm_KbkList.dfm', Fm_KbkList);
      CdsList := TClientDataSet(Fm_KbkList.FindComponent('CdsData'));
      DbGrid := TDBGridEh(Fm_KbkList.FindComponent('DbGrid'));
      EdtCdsId := TEdit.Create(Fm_KbkList);
      tlbFilterClear := TToolButton(Fm_KbkList.FindComponent('tlbFilterClear'));
      tlbFilter := TToolButton(Fm_KbkList.FindComponent('tlbFilter'));
      tlbRefresh := TToolButton(Fm_KbkList.FindComponent('tlbRefresh'));
      // скрываем панель тулбара
      PanelFilter := TPanel( Fm_KbkList.FindComponent('pnlTollBar'));
      if PanelFilter.Visible then PanelFilter.Hide;
      // скрываем панель фильтра
      PanelFilter := TPanel(Fm_KbkList.FindComponent('PanelFilter'));
      if PanelFilter.Visible then PanelFilter.Hide;
      tlbFilterClear.OnClick := @tlbFilterClearOnClick;
      tlbFilter.OnClick := @tlbFilterOnClick;
      edKesCode := TEdit(Fm_KbkList.FindComponent('edKesCode'));
      edKadmCode := TEdit(Fm_KbkList.FindComponent('edKadmCode'));
      edKesCode.OnChange := @edKesCodeOnChange;
      edKadmCode.OnChange := @edKadmCodeOnChange;
      RadioTypeKbk := TRadioGroup(Fm_KbkList.FindComponent('RadioTypeKbk'));
      RadioTypeKbk.OnClick := @RadioTypeKbkOnClick;
      spbKesCode := TSpeedButton(Fm_KbkList.FindComponent('spbKesCode'));
      spbKadmCode := TSpeedButton(Fm_KbkList.FindComponent('spbKadmCode'));
      spbKesCode.OnClick := @spbKesCodeOnClick;
      spbKadmCode.OnClick := @spbKadmCodeOnClick;
      Fm_KbkList.Edit := EdtCdsId;
      Fm_KbkList.SQLText := '';
      Fm_KbkList.OnHide := @Fm_KbkListOnHide;
      Fm_KbkList.Caption := 'Список выгружаемых кодов бюджетной классификации...';
      Fm_KbkList.Show;
      Fm_KbkList.Close;
    end;

  finally
    SL.Free;
  end;
  // форма сообщений об ошибках проверки данных
  InitializeMessageValid( 'Exp_Fk_KBK_NEW', DirIni);
end; {End Initialize}

procedure OkBtnOnClick(Sender: TObject);
begin
  UnloadKbk := UnloadKbk_Chbx.Checked;
  UnloadKcsr := UnloadKcsr_Chbx.Checked;
  UnloadKvr := UnloadKvr_Chbx.Checked;
  UnloadSubDohIst := UnloadPodvid_Chbx.Checked;
  UnloadGlava := UnloadGlava_Chbx.Checked;
  UnloadGrantInvestment := UnloadGrantInvestment_Chbx.Checked;

  // exchange #2473
  vUnloadPurposefulgrant := vUnloadPurposefulgrant_Chbx.Checked;

  Fm_ParamUnloadKbk.ModalResult := MrOk;
end; {End OkBtnOnClick}

procedure CancelBtnOnClick(Sender: TObject);
begin
  Fm_ParamUnloadKbk.ModalResult := MrCancel;
end; {End CancelBtnOnClick}

procedure tlbFilterClearOnClick(Sender: TObject);
begin
  CdsList.Filter := '';
  RadioTypeKbk.ItemIndex := -1;
  EdKesCode.Text := '';
  EdKadmCode.Text := '';
  if DBGrid.Visible then
    DBGrid.SetFocus;
  DBGrid.SumList.Active := False;
  DBGrid.SumList.Active := True;
end; {End tlbFilterClearOnClick}

procedure tlbFilterOnClick(Sender: TObject);
begin
  PanelFilter.Visible := tlbFilter.Down;
  tlbFilter.Hint := Ifthen(PanelFilter.Visible, 'Скрыть панель фильтрации',
    'Показать панель фильтрации');
end; {End tlbFilterOnClick}

procedure spbKesCodeOnClick(Sender: TObject);
begin
  EdKesCode.Text := GetKbkForFilter('Kes');
end; {End spbKesCodeOnClick}

procedure spbKadmCodeOnClick(Sender: TObject);
begin
  EdKadmCode.Text := GetKbkForFilter('Kadm');
end; {End spbKadmCodeOnClick}

//возвращает строку с перечисленными через запятую КБК.
function GetKbkForFilter(TableName: String): String;
var
  Fm_FilterKbk: TFormCustomList;
  EdFilter: TEdit;
  tlbRefresh: TToolButton;
  CdsFilterData: TClientDataSet;
  DBGridFilter: TDBGridEh;
  procedure Fm_FilterKbkOnActivate(Sender: TObject);
  var
    FilterXml: Variant;
    sql: String;
    iRow: Integer;
  begin
    FilterXml := CreateOleObject('Microsoft.XmlDom');
    sql := 'Select K.Code, L.Caption, L.Description,' +
      ' L.Begin_Date, L.End_Date, L.Change_Date' +
      ' From ' + TableName + ' K' +
      ' Join ' + TableName + '_Lines L on K.Id = L.Master_Id' +
      ' Where K.Budget_Id = ? and (L.End_Date is null or L.End_Date >= ?)' +
      ' and L.Begin_Date <= ? and L.Unapproved = 0';
    Query.AddStr(BudgetId);
    Query.AddDate(Work_Date);
    Query.AddDate(Work_Date);
    FilterXml.LoadXml(Query.Run(sql));
    FilterXml := FilterXml.documentElement;
    //дописывание нулей впереди
    if Sender is TToolButton then
      DBGridFilter.SavePosition;
    CdsFilterData.DisableControls;
    DBGridFilter.Enabled := False;
    CdsFilterData.First;
    while not CdsFilterData.Eof do
      CdsFilterData.Delete;
    for iRow := 0 to FilterXml.childNodes.Length - 1 do
    begin
      FilterXml := FilterXml.childNodes.Item[iRow];
      CdsFilterData.Append;
      CdsFilterData.FieldByName('CODE').AsString := StringReplace(
          Format('%3s', [FilterXml.getAttribute('CODE')]), ' ', '0');
      CdsFilterData.FieldByName('CAPTION').AsString := IfThen(
        FilterXml.getAttribute('CAPTION') <> '',
          FilterXml.getAttribute('CAPTION'), '');
      CdsFilterData.FieldByName('DESCRIPTION').AsString := IfThen(
        FilterXml.getAttribute('DESCRIPTION') <> '',
          FilterXml.getAttribute('DESCRIPTION'), '');
      CdsFilterData.FieldByName('BEGIN_DATE').AsDateTime :=
        XmlToDateTime(FilterXml.getAttribute('BEGIN_DATE'));
      CdsFilterData.FieldByName('CHANGE_DATE').AsDateTime :=
        XmlToDateTime(FilterXml.getAttribute('CHANGE_DATE'));
      if (FilterXml.getAttribute('END_DATE') <> '') then
        CdsFilterData.FieldByName('END_DATE').AsDateTime :=
          XmlToDateTime(FilterXml.getAttribute('END_DATE'));
      CdsFilterData.Post;
      FilterXml := FilterXml.parentNode;
    end;
    CdsFilterData.First;
    DBGridFilter.Enabled := True;
    CdsFilterData.EnableControls;
    if Sender is TToolButton then
      DBGridFilter.RestorePosition;
  end;
  procedure Fm_FilterKbkOnHide(Sender: TObject);
  begin
    EdFilter.Text := DbGrid.Bookmark.CommaText;
  end;
begin
  Result := '';
  Fm_FilterKbk := TFormCustomList.Create(Application);
  EdFilter := TEdit.Create(Fm_FilterKbk);
  try
    LoadForm(DirIni + 'Forms\Fm_KbkList.dfm', Fm_FilterKbk);
    Fm_FilterKbk.Caption := 'Фильтр по бюджетной классификации';
    if UpperCase(TableName) = 'KES' then
    begin
      Fm_FilterKbk.Caption := Fm_FilterKbk.Caption + ': КОСГУ';
      Result := edKesCode.Text;
    end
    else begin
      Fm_FilterKbk.Caption := Fm_FilterKbk.Caption + ': Код главы';
      Result := edKadmCode.Text;
    end;
    Fm_FilterKbk.Edit := EdFilter;
    tlbRefresh := TToolButton(Fm_FilterKbk.FindComponent('tlbRefresh'));
    tlbRefresh.Visible := True;
    tlbRefresh.OnClick := @Fm_FilterKbkOnActivate;
    CdsFilterData := TClientDataSet(Fm_FilterKbk.FindComponent('CdsData'));
    DBGridFilter := TDBGridEh(Fm_FilterKbk.FindComponent('DBGrid'));
    DBGridFilter.FieldKey := CdsFilterData.FieldByName('Code');
    Fm_FilterKbk.OnActivate := @Fm_FilterKbkOnActivate;
    Fm_FilterKbk.OnHide := @Fm_FilterKbkOnHide;
    if (Fm_FilterKbk.ShowModal = MrOk) and (EdFilter.Text <> '') then
      Result := '''' + StringReplace(EdFilter.Text, ',', ''',''') + ''''
  finally
    EdFilter.Free;
    Fm_FilterKbk.Free;
  end;
end; {End GetKbkForFilter}

//метод для установления фильтров.
procedure setKbkFilter;
var
  PFilter, Filter: String;
//  xFilterChanges: integer;
//  xXml: Variant;
begin
  try
    DBGrid.BookmarkUnCheckAll;
//    CdsList.Filter := '';
    Filter := '';
    case RadioTypeKbk.ItemIndex of
      0: PFilter := 'TYPE_KBK = 20';//10';
      1: PFilter := 'TYPE_KBK = 10';//20';
      2: PFilter := 'TYPE_KBK in (31, 32)';
    else
      PFilter := '';
    end;
    if edKesCode.Text <> '' then
    begin
      Filter := 'KesCode in (' + edKesCode.Text + ')';
      if edKadmCode.Text <> '' then
        Filter := Filter + ' and KadmCode in (' + edKadmCode.Text + ')';
    end
    else if edKadmCode.Text <> '' then
      Filter := 'KadmCode in (' + edKadmCode.Text + ')';
    if (PFilter <> '') and (Filter <> '') then
      Filter := Filter + ' and ' + PFilter
    else if PFilter <> '' then Filter := PFilter;

    CdsList.Filter := Filter;
    InsertKBKListToCds(
          CdsList,
          DBGrid,
          LoadKBKFromDictionaries( BudgetId, rgFilterKBK.ItemIndex),
          BudgetId);
    {
    DBGrid.Enabled := False;
    CdsList.DisableControls;
    CdsList.Filter := Filter;
  DBGrid.Enabled := True;
  CdsList.EnableControls;
  CdsList.First;
  CdsList.Open;
  }
{    //фильтр в зависимости от выбора типа КБК
    case RadioTypeKbk of
      0: xXml := LoadKBKFromDictionaries( BudgetId, rbFilterKbk.ItemIndex);
      1: xXml := LoadKBKFromDictionaries( BudgetId, rbFilterKbk.ItemIndex, FALSE, FALSE, TRUE);
      2: xXml := LoadKBKFromDictionaries( BudgetId, rbFilterKbk.ItemIndex, FALSE, TRUE, FALSE);
    end;}
//    Log( FORMAT( 'Фильтр (CdsList.Filter): "%s".', [ Filter]));
  except
    MessageDlg('Ошибка при установлении фильтра. Количество данных фильтра' +
      ' превышает допустимое значение', mtError, mbOk, 0);

    CdsList.Filter := '';
    InsertKBKListToCds( CdsList,
          DBGrid,
          LoadKBKFromDictionaries( BudgetId, 0),
          BudgetId);
    tlbFilterClearOnClick(nil);
  end;
  DBGrid.SumList.Active := False;
  DBGrid.SumList.Active := True;
end;

procedure edKesCodeOnChange(Sender: TObject);
begin
  setKbkFilter;
end; {End edKesCodeOnChange}

procedure edKadmCodeOnChange(Sender: TObject);
begin
  setKbkFilter;
end; {End edKadmCodeOnChange}

procedure RadioTypeKbkOnClick(Sender: TObject);
begin
  setKbkFilter;
end;

{
procedure RadioTypeKbkOnClickEx(Sender: TObject);
var
  xXml: Variant;
begin
//  DBGrid.BookmarkUnCheckAll;
  //фильтр в зависимости от выбора типа КБК
  case RadioTypeKbk.ItemIndex of
    0: xXml := LoadKBKFromDictionaries( BudgetId, TRUE, FALSE, FALSE);
    1: xXml := LoadKBKFromDictionaries( BudgetId, FALSE, FALSE, TRUE);
    2: xXml := LoadKBKFromDictionaries( BudgetId, FALSE, TRUE, FALSE);
  end;
  InsertKBKListToCds( CdsList, DBGrid, xXml);
//  DBGrid.SumList.Active := False;
//  DBGrid.SumList.Active := True;
end; {End RadioTypeKbkOnClick}


//получает выбранные КБК из Dataset в Xml. Структура Xml приближена к структуре
//Xml, получаемой при выборке КБК из базы
function GetSelectedKbk(CdsList: TClientDataSet; var CdsIdList: String): Variant;
var
  i: Integer;
  //делит список Id в передаваемой строке StrId на количество ValueCountId
  //Если StrId = 1,2,3,4 и ValueCountId = 3. После выполнения функции получим
  //Result = 1,2,3 и StrId = 4
  function DelimitStrIdByCount(var StrId: String; ValueCountId: Integer): String;
  const
    Delimiter = ','; //разделитель Id.
  var
    j, CountId: Integer;
  begin
    Result := '';
    CountId := 0;
    j := 1;
    while (j <= Length(StrId)) and (CountId < ValueCountId) do
    begin
      if StrId[j] = Delimiter then
        Inc(CountId);
      Inc(j);
    end;
    if CountId = ValueCountId then
    begin
      Result := Copy(StrId, 1, j - 2);
      Delete(StrId, 1, j - 1);
    end
    else begin
      Result := StrId;
      StrId := '';
    end;
  end;
begin
  Result := null;
  while CdsIdList <> '' do
  begin
    CdsList.Filter := 'CDSID in(' + DelimitStrIdByCount(CdsIdList, 2000) + ')';
    if CdsList.RecordCount <= 0 then
    begin
      CdsList.Filter := '';
      CdsIdList := '';
      Exit
    end
    else if VarIsNull(Result) then
    begin
      Result := CreateOleObject('Microsoft.XmlDom');
      Result.appendChild(Result.createElement('EXCHQUERY'));
      Result := Result.documentElement;
    end;
    CdsList.First;
    for i := 0 to CdsList.RecordCount - 1 do
    begin
      Result := Result.appendChild(Result.ownerDocument.createElement('ROW'));
      Result.setAttribute('CODE', CdsList.FieldByName('CODE').AsString);
      Result.setAttribute('CAPTION', CdsList.FieldByName('CAPTION').AsString);
      Result.setAttribute('DESCRIPTION',
        CdsList.FieldByName('DESCRIPTION').AsString);
      Result.setAttribute('BEGIN_DATE',
        DateTimeToXml(CdsList.FieldByName('BEGIN_DATE').AsDateTime));
      Result.setAttribute('CHANGE_DATE',
        DateTimeToXml(CdsList.FieldByName('CHANGE_DATE').AsDateTime));
      Result.setAttribute('END_DATE',
        IfThen(CdsList.FieldByName('END_DATE').AsString <> '',
          DateTimeToXml(CdsList.FieldByName('END_DATE').AsDateTime), ''));
      Result.setAttribute('TYPE_KBK', CdsList.FieldByName('TYPE_KBK').AsString);
      Result.setAttribute('LIMIT', CdsList.FieldByName('LIMIT').AsString);
      Result := Result.parentNode;
      CdsList.Next;
    end;
    Result := Result.ownerDocument;
  end;
  CdsList.Filter := '';
  CdsIdList := '';
end;

//добавляет данные отобранные КБК в Dataset
//Kbk20 - флаг 20-тизначного КБК (кодов доходов бюджета, кодов расходов и
//кодов источников финансирования дефицитов бюджетов)
procedure InsertDataToCds( CdsList: TClientDataSet;
                           var DBGrid: TDBGridEh;
                           Xml: Variant;
                           Kbk20: Boolean = False);
var
  i: Integer;
  s1,s2,s3: string;
begin
  CdsList.DisableControls;
  DBGrid.Enabled := False;
  CdsList.First;
  while not CdsList.Eof do
    CdsList.Delete;
  Xml := Xml.documentElement;
  for i := 0 to Xml.childNodes.Length - 1 do
  begin
    Xml := Xml.childNodes.Item[i];
    CdsList.Append;
    CdsList.FieldByName('CDSID').AsInteger := i;
    CdsList.FieldByName('CODE').AsString := Xml.getAttribute('CODE');
    CdsList.FieldByName('CAPTION').AsString := IfThen(
      Xml.getAttribute('CAPTION') <> '', Xml.getAttribute('CAPTION'), '');
    CdsList.FieldByName('DESCRIPTION').AsString := IfThen(
      Xml.getAttribute('DESCRIPTION') <> '', Xml.getAttribute('DESCRIPTION'), '');
    CdsList.FieldByName('BEGIN_DATE').AsDateTime :=
      XmlToDateTime(Xml.getAttribute('BEGIN_DATE'));
    CdsList.FieldByName('CHANGE_DATE').AsDateTime :=
      XmlToDateTime(Xml.getAttribute('CHANGE_DATE'));
    if (Xml.getAttribute('END_DATE') <> '') then
      CdsList.FieldByName('END_DATE').AsDateTime :=
        XmlToDateTime(Xml.getAttribute('END_DATE'));
    if not VarIsNull(Xml.getAttribute('TYPE_KBK')) then
      CdsList.FieldByName('TYPE_KBK').AsString := Xml.getAttribute('TYPE_KBK');
    if not VarIsNull(Xml.getAttribute('LIMIT')) then
      CdsList.FieldByName('LIMIT').AsString := Xml.getAttribute('LIMIT');
    if Kbk20 then
    begin
      CdsList.FieldByName('KADMCODE').AsString :=
        Copy(Xml.getAttribute('CODE'), 1, 3);
      CdsList.FieldByName('KESCODE').AsString :=
        Copy(Xml.getAttribute('CODE'), 18, 3);
    end;
    CdsList.Post;
    Xml := Xml.parentNode;
  end;
  CdsList.First;
  DBGrid.Enabled := True;
  CdsList.EnableControls;
end;

//todo подумать как переделать инициализацию формы без Show и Hide.
//Как вариант повесить формирование Dataset на OnActivate
{procedure Fm_KbkListOnActivate(Sender: TObject);
begin
  //Если кнопака фильтрации видна, то выгружаем 20-тизначный код. Фильтры только
  //в 20-тизначных кодах, учесть/переписать в случае изменения выгрузки в части
  //фильтров для других кодов
  if tlbFilter.Visible then
    InsertDataToCds(CdsList, DBGrid, XmlListCodeforCds.ownerDocument, True)
  else
    InsertDataToCds(CdsList, DBGrid, Kbk);
end;}

//перекрывает событие скрытия формы. Здесь обнуляем DbGrid.Bookmark, иначе
//он сохраняется при следующем открытии формы
procedure Fm_KbkListOnHide(Sender: TObject);
begin
  KbkIdList := DbGrid.Bookmark.CommaText;
  DbGrid.Bookmark.Clear;
end;

//перекрывает событие скрытия формы. Здесь обнуляем DbGrid.Bookmark, иначе
//он сохраняется при следующем открытии формы
procedure Fm_KbkListOnHideEx(Sender: TObject);
begin
  KbkIdList := DbGrid.Bookmark.CommaText;
//  DbGrid.Bookmark.Clear;
end;

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

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