uses
  'Un_Log';

const
  { константы для форматирования кодов БК по маске в функции FormatKbk.
     kbkMaskDefault - только получения кода по длине (с добавлением нулей слева)
                        ( по умолчанию);
     kbkMaskOfChar - форматирование кода по маске.
                      перед этим выполняется удлинение кода с добавлением 0-х
                      символов (как с kbkMaskDefault), а затем выполняется
                      форматирование.
    }
  kbkMaskDefault = 0;
  kbkMaskOfChar = 1;

  // нулевой код БК
  // считаем что самый длинный нулевой код не будет длинее 70 символов.
  // тогда дополнение када до нужной длины будем выполнять копированием
  // подстроки из этой константы.
  // код длиннее этой строки дополняется посимвольно.
  cnKBK_Null_Str =
       '000000000000000000000000000000000000000000000000000000000000000000000';

var
  { Exchange #2362,#2363,#2364,#2365,#2366,#2367,#2368
    идентификатор системного кода субсидии "не указано".
    заполняется функцией GetGrantInvestmentID_SystemNull.}
  vGrantInvestmentID_SystemNull: string;

  // exchange #2615
  vCfgModeSearchOtrKod: string;
  
  // Exchange, #3215
  vCfg_FindGrInvCodeBySymbols: string;
  
  vCfgModeSameMethodKesrAndKDMA: boolean;

  // exchange #3036
  // для оптимизации некоторые глобальные переменные перенесены сюда
  // чтобы они были видны в других модулях
  vIsAzkVersion2_30: boolean;
  vIsAzkVersion2_29: boolean;
  vIsAzkVersion2_26_46_1: boolean;
  vIsAzkVersion2_26_42_11: boolean;
  vIsAzkVersion2_26_46_11: boolean;


{~QueryStr
 Функция низжего уровня (основа для остальных) - возвращает строку
 sqlText - SQL запрос формата 'Select .. From .. Where .. = ?'
 parValue - список параметров подставляемые вместо ? в sqlText,
  например [..,..,..,]}
function QueryStr(sqlText: String; parValue: Variant = Null): String;
var
  xQuery: TRunQuery;

  procedure parValueAdd(parValue: Variant);
  var i: Integer;
  begin
    if VarIsNull(parValue) then Exit;
    if VarIsArray(parValue)
      then
        begin
          for i := 0 to Length(parValue) - 1
            do parValueAdd(parValue[i]);
        end
      else
        // exchange 2690
        // здесь вместо проверки значения даты проверямем тип variant
        // и только если это дата - только тогда значение даты.
        // если этого не делать а проверять, как раньше, только значение
        // даты, то вещественное число, подходяшее по формату ДД.ММ,
        // воспринимается как дата.
        // например сумма 18.03 (если разделитель в строке даты - точка),
        // воспринимается как дата 1900-01-17. преобразование выполняется
        // неявно через строку: 18.03 число -> ValidDate( 18.03) =true
        // что в результате приводит к типу дата.
        if (VarType(parValue) <> varDate)  //ValidDate(parValue)
          then xQuery.AddStr( VarToStr(parValue))
          else
            if ValidDate(parValue)
              then xQuery.AddDate( parValue)
              else xQuery.AddStr( VarToStr(parValue));
  end;
begin
  try
    xQuery:= TRunQuery.Create;
    try
      parValueAdd(parValue);
      Result := xQuery.Run(sqlText);
      finally
        xQuery.Free;
    end;
    except
      raise( 'Сбой QueryStr: ' + LastException.Message);
  end;
end; {End QueryStr}

{~QueryXml
 Функция обёртка над QueryStr, отличие возвращает XML
 При отсутствие результата выборки: Result.documentElement.childNodes.length = 0}
function QueryXml(sqlText: String; parValue: Variant = Null): Variant;
begin
  try
    Result := CreateOleObject('Microsoft.XmlDom');
    Result.LoadXML(QueryStr(sqlText, parValue));
    except
      raise( 'Сбой QueryXml: ' + LastException.Message);
  end;
end; {End QueryXml}

{~QueryField
 Функция обёртка над QueryXml, отличие  - возвращает значение поля
 Добавлен параметр fldName - наименование поля, в случае}
function QueryField(sqlText: String; parValue: Variant; fldName: String): String;
var
  Xml: Variant;
begin
  try
    Xml := QueryXml(sqlText, parValue);
    if Xml.documentElement.ChildNodes.length = 0 then
    begin
      Result := '';
      Exit;
    end;
    result := Xml.documentElement.firstChild.getAttribute(fldName);
      if result = null then
        raise('Поля: ' + fldName + ' нет в результирующей XML по запросу: ' + SQLText);
    except
      raise ( 'Сбой QueryField: ' + LastException.Message);
  end;
end;


{~FormatKbk
  получение форматированного кода aCode с наименованием в таблице
  шаблонов: aCodeName для бюджета aBudget_Id.

  это усеченная функция (без кеширования), поскольку требуется определить
  только нулевой код субсидии в функции GetGrantInvestmentID_SystemNull.

  параметры:
    aFormatType - =0 - указывает что код нужно форматировать по длине
                      (добавление нулей слева).
                      длина кода берется из маски.
                  =1 - указывает что код нужно форматиовать по маске.}
function FormatKbk( aCodeName, aCode, aBudget_Id: String;
                    aFormatType: Integer = 0): String;
var
  xCodeMask: string;
  xCodeLength: integer;
  xCodeNull: string;
begin
    try
      GetFormatKbkLengthAndMask( aCodeName,
                                 aBudget_Id,
                                 xCodeLength,
                                 xCodeMask,
                                 xCodeNull);

      // если код указан нулевым, т.е. требуется вернуть форматированный
      // нулевой код, тогда возвращаем нулевой код.
      if ( aCode = '0')
        then Result := xCodeNull
        else begin
          // разница длины строки входного кода и длины кода дает
          // количество нулевых символов для форматирования кода по длине
          // относительно нулевого кода.
          xCodeLength := xCodeLength - LENGTH( aCode);
          if ( xCodeLength <= 0)
            then Result := aCode
            else Result := COPY( xCodeNull, 1, xCodeLength) + aCode;
        end;

      // если требуется вернуть форматированный код, то форматируем код по
      // маске кода из кеша.
      if (aFormatType = kbkMaskOfChar)
        then Result := FormatMaskText( xCodeMask, Result);
      except
      raise ( 'Сбой FormatKbk: ' + LastException.Message);
    end;
end;


{~GetFormatKbkLengthAndMask
  получение длины, маски и нулевого кода для указанного кода КБК aCodeName.
  результат:
    aCodeLength - длина кода
    aMask - полная маска для функции FormatMaskText
    aNullCode - нулевой код (без форматирования по маске).}
procedure GetFormatKbkLengthAndMask( aCodeName, aBudget_Id: String;
                                     var aCodeLength: integer;
                                     var aMask: string;
                                     var aNullCode: string);
var
  i: integer;
  xCodeLengthStr: string;
  xXmlAplabet: Variant;
  xMaskAplabet: string;
begin
    try
      // нулевой код пока не определен но здесь ВАЖНО инициализировать строку.
      aNullCode := '';

      // загружаем длину и маску из бд
      aMask := QueryField( 'Select Tree_Mask From CodeType Where Name = ? '
                          +'and Budget_Id = ?',
                          [ aCodeName,
                            aBudget_Id],
                          'TREE_MASK');
      // длина кода из поля в таблице масок
      xCodeLengthStr := QueryField( 'Select TREE_MASK_LENGTH From CodeType '
                                   +'Where Name = ? and Budget_Id = ?',
                                    [ aCodeName,
                                      aBudget_Id],
                                    'TREE_MASK_LENGTH');
      // если длина прописана явно - берем ее.
      // если нет - будем вычислять по маске.
      if (LENGTH( TRIM( xCodeLengthStr)) <= 0)
        then aCodeLength := 0
        else aCodeLength := STRTOINT( xCodeLengthStr);


      { здесь загружаем все названия строк символов в масках кодов БК.
        фактически это является алфовитом масок.
        это нужно чтобы понять, какие символы в строке масок являются
        шаблонами для символов, а какие разделителями.
        символы в маске, которые отсутствуют в загруженном алфавите
        являются разделителями.
        например:
          алфавит в БД: Aж
          маска: AA.ж
        символ "." не входит в алфавит и является разделителем,
        поэтому длина кода по маске =3, а не 4!}
      xXmlAplabet := QueryXml( 'select bm.name from budgetmask bm where bm.budget_id=?',
                                [ aBudget_Id]);
      xXmlAplabet := xXmlAplabet.documentElement;
      for i := 0 to xXmlAplabet.ChildNodes.length - 1
        do xMaskAplabet := xMaskAplabet
                            + xXmlAplabet.ChildNodes.item[ i].GetAttribute( 'NAME');

      if (aCodeLength <= 0) then begin
        // если длина явно не указана в БД - расчитываем по маске.
        // для этого в начале определяем длину символьной маски, а затем
        // исключаем символы, которых нет в алфавите масок
        aCodeLength := LENGTH( aMask);
        // проверяем символы строки маски на вхождение в алфавит
        // те символы которые не входят в алфавит - исключаем из длины.
        for i := 1 to LENGTH( aMask) do
          if (POS( aMask[i], xMaskAplabet) <= 0)
            then DEC( aCodeLength);
      end;

      if ( aCodeLength <= 0) then begin
      {
        WriteInfo( FORMAT( 'Ошибка определения длины кода %s бюджета %s.',
                            [ aCodeName,
                              aBudget_Id]));
                              }
        EXIT;
      end;

      // получение нулевого кода, отформатированного по длине.
      // в реализации с кешем, сохраняется именно такой код, а не длина.
      // длину можно определить по длине строки нулевого кода.
      if ( aCodeLength < LENGTH( cnKBK_Null_Str))
        then aNullCode := COPY( cnKBK_Null_Str, 1, aCodeLength)
        else
          while (LENGTH( aNullCode) < aCodeLength)
            do aNullCode := aNullCode + '0';

      // если нужно отобразить код по маске, получаем набор символов маски,
      // и те символы, которые описывают наборы символов маски заменяем на
      // символ A, а прочие - оставляем как есть.
      // тем самым будет получена маска для функции FormatMaskText.
      // поскольку в эксче точку не закладываем как разделитель, то
      // те символы которые описаны как подмножества, определяются как маска
      // под символ кода, а прочие - фиксированные символы.
      // в начале получаем все символы для маски, маскирующие символы кода
      // в строку xMaskAplabet.
      for i := 1 to LENGTH( aMask) do
        if (POS( aMask[i], xMaskAplabet) > 0)
          then aMask[i] := 'A';
      // формируем маску для функции FormatMaskText
      // эта же строка пойдет в кэш для маски.
      aMask := aMask + ';0;*';
      except
        ExceptionProc( 'GetFormatKbkLengthAndMask');
    end;
end;


{~GetMaskIndustryCode
  возвращает отраслевой код aCode с учетом маски для бюджета aBudget_Id.
  работает начиная с версии АЦК-Ф 2.32.2.17}
function GetMaskIndustryCode( aCode, aBudget_Id: string): string;
begin
    try
      Result := FormatKbk( 'INDUSTRYCODE', aCode, aBudget_Id, 0);
      except
        ExceptionProc( 'GetMaskIndustryCode');
    end;
end;


{~GetMaskGrantInvestmentCode
  возвращает код субсидии aCode с учетом маски для бюджета aBudget_Id.}
function GetMaskGrantInvestmentCode( aCode, aBudget_Id: string): string;
begin
    try
      Result := FormatKbk( 'GRANTINVESTMENT', aCode, aBudget_Id, 0);
      except
        ExceptionProc( 'GetMaskGrantInvestmentCode');
    end;
end;

{~GetGrantInvestmentID_SystemNull
  Exchange #2376
  возвращает идентификатор системного кода субсидии "не указано".
  Если такого значения в справочнике не будет, будет возвращена пустая строка.
  запись системного кода субсидии "не указано" в справочнике «Субсидии и
  инвестиции» со следующими параметрами:
   •	Код 0,
   •	наименование «Не указан»,
   •	тип 3«Не указан»,
   •	Учредитель «Не указана»

   функция совместима с версиями АЦК-финансы <2.29 но уже в 2.28 значение
поля grantinvestment.ftype ограничено значением [0,1,2], поэтому результат
этой функции всегда будет отрицательным.}
function GetGrantInvestmentID_SystemNull( aBudget_Id: string): boolean;
var
  xXml: Variant;
  xRootXml: Variant;
begin
    try
      xXml := CreateOleObject('Microsoft.XmlDom');
      try
        // exchange #2627
        // при переходе на символьные КБК, нулевой код содержит не код 0,
        // а код с количеством нулей, описанных в маске GRANTINVESTMENT
        // в справочнике "виды классификаторов".
        Query.AddStr( FormatKbk( 'GRANTINVESTMENT', '0', aBudget_Id, 0));
        Query.AddStr( '%не указан%');
        Query.AddStr( aBudget_Id);
        xXml.LoadXML(
            Query.Run(
                 ' select g.id from grantinvestment g'
                +' where g.code=? and g.ftype=3 '
                // exchange #2416
                // по умолчанию могут назвать какое угодно окончание, поэтому
                // в запросе указан шаблон вместо явной строки.
                +' and (lower(trim(g.caption)) like ?)'
                +' and (g.org_id=0 or g.org_id is null)'
                +' and g.budget_id=?'));
        xRootXml := xXml.documentElement;
        if (xRootXml.childNodes.Length <> 1)
          then vGrantInvestmentID_SystemNull := ''
          else vGrantInvestmentID_SystemNull :=
                                    xRootXml.childNodes.Item[0].getAttribute( 'ID');
        Result := ( LENGTH( vGrantInvestmentID_SystemNull) > 0);
        finally
          xXml := NIL;
      end;
      except
        ExceptionProc( 'GetGrantInvestmentID_SystemNull');
    end;
end;

{~DoGrantInvestmentCodeIsSystem
  Exchange #2363
  возвращает =true, если для текущей записи код субсидии является системным.
  т.е. удовлетворяет критерию:
    •	Код 0,
    •	наименование «Не указан»,
    •	тип 3«Не указан»,
    •	Учредитель «Не указана».
  функция выполняет сравнение поля GrantInvestment_Id на идентификатор
  системного кода в справочнике "субсидии и инвестиции".}
function DoGrantInvestmentCodeIsSystem{( cds: TClientDataSet)}: boolean;
begin
    try
      Result := (CDSListField_Grantinvestment_Id_Get{cds.FieldByName('GrantInvestment_Id').AsString}
                   = vGrantInvestmentID_SystemNull);
      except
        ExceptionProc( 'DoGrantInvestmentCodeIsSystem');
    end;
end;


{~DoSearchGrantInvestmentCode_
  возвращает =true, если найдено описание кода в справочнике "субсидии и
  инвестиции". при этом в записи сохраняется найденный идентифиатор.
  описание типа кода в записи не сохраняется.
  параметры:
    aWithOrgNull
        =TRUE то допускается указание организации как "не указана" (Org_ID=0)
              но при этом код субсидии должен быть обязателен.
        =FALSE - проверяется только указание кода организации и кода субсидии.

  exchange #2655
    в результате функция разделена на две функции, поскольку есть
    проблемы с рекурсивным вызовом.}
function DoSearchGrantInvestmentCode_( //cds: TClientDataSet;
                                       aWithOrgNull: boolean): integer;
var
  xXml: Variant;
begin
    try
      try
        xXml := CreateOleObject('Microsoft.XmlDom');
        if aWithOrgNull then begin
          // красноярский край
          Query.AddStr( CDSListField_Budget_Id_Get);//cds.FieldByName('Budget_Id').AsString);
          Query.AddStr( CDSListField_Org_Id_Get);//cds.FieldByName('Org_Id').AsString);
          Query.AddStr( CDSListField_Grantinvestment_Code_Get);//cds.FieldByName('Grantinvestment_Code').AsString);
          Query.AddStr( CDSListField_Budget_Id_Get);//cds.FieldByName('Budget_Id').AsString);
          Query.AddStr( CDSListField_Grantinvestment_Code_Get);//cds.FieldByName('Grantinvestment_Code').AsString);
          xXml.LoadXML(
              Query.Run(
                    ' Select G.Id' +
                    ' From Grantinvestment G' +
                    ' Join Org O on O.Manager_Id = G.Org_id' +
                    ' Where G.Budget_Id = ? and O.Id = ? and G.Code = ?' +
                    ' Union Select G.Id' +
                    ' From Grantinvestment G' +
                    ' Where G.Budget_Id = ? and G.Org_Id = 0 and G.Code = ?'));
        end else begin
          Query.AddStr( CDSListField_Budget_Id_Get);//cds.FieldByName('Budget_Id').AsString);
          Query.AddStr( CDSListField_Org_Id_Get);//cds.FieldByName('Org_Id').AsString);
          Query.AddStr( CDSListField_Grantinvestment_Code_Get);//cds.FieldByName('Grantinvestment_Code').AsString);
          xXml.LoadXML(
              Query.Run(
                   ' Select G.Id From Grantinvestment G'
                  +' Join Org O on O.Manager_Id = G.Org_id'
                  +' Where G.Budget_Id = ? and O.Id = ? and G.Code = ?'));
        end;
        Result := xXml.documentElement.childNodes.Length;
        if (Result = 1) then begin
          CDSListField_Grantinvestment_Id(
                     xXml.documentElement.childNodes.Item[0].getAttribute('ID'));
        end
        finally
          xXml := NIL;
      end;
      except
        ExceptionProc( 'DoSearchGrantInvestmentCode_');
    end;
end;

{~DoSearchGrantInvestmentCode
  возвращает =true, если найдено описание кода в справочнике "субсидии и
  инвестиции". при этом в записи сохраняется найденный идентифиатор.
  описание типа кода в записи не сохраняется.
  параметры:
    aWithOrgNull
        =TRUE то допускается указание организации как "не указана" (Org_ID=0)
              но при этом код субсидии должен быть обязателен.
        =FALSE - проверяется только указание кода организации и кода субсидии.

  exchange #2655
    для основного алгоритма поиск выполняется в начале для организации,
    а потом с учетом системного кода.
        aDivideSearch - раздельный поиск (по exchange #2655)
            =true - поиск кода цели в начале ищется по организации
                    (без второго подзапроса с системным кодом).
                    если в результате код будет обнаружен однозначно,
                    то возвращаем его. если будет неоднозначность - запускаем
                    поиск с учетом системного кода.
            =false - выполняется запрос по старому.
    в результате функция разделена на две функции, поскольку есть
    проблемы с рекурсивным вызовом.}
function DoSearchGrantInvestmentCode( aWithOrgNull: boolean;
                                      aDivideSearch: boolean = FALSE): integer;
begin
    try
      if (aWithOrgNull AND aDivideSearch) then begin
        // exchange #2655
        // если указано искать раздельно: в начале проверить код только
        // для вышестоящего ГРБС у орг., то проверяем раздельно от
        // подзапроса системного кода.
        // если найдено однозначно - возвращаем результат =1 (код найден),
        // если среди орг. найдено больше одного кода, то сист. код не ищем.
        Result := DoSearchGrantInvestmentCode_( {cds, }FALSE);
        // если среди орг. не найдено кода, то ищем с учетом системного кода
        if (Result < 1)
          then Result := DoSearchGrantInvestmentCode_( {cds, }aWithOrgNull);
      end else begin
        Result := DoSearchGrantInvestmentCode_( {cds, }aWithOrgNull);
      end;
      except
        ExceptionProc( 'DoSearchGrantInvestmentCode');
    end;
end;


{~DoSetGrantInvestmentCode
  установка значения кода субсидии.
  при этом чистятся поля типа и идентификатора кода - ссылка на справочник.
  это однозначно дает возможность понять, было ли изменение кода (т.е. получено
  значение из какого нибудь поля) или нет после начальной установки отраслевого
  кода.}
procedure DoSetGrantInvestmentCode( aCode: string);
begin
    try
      CDSListField_Grantinvestment_Code( aCode);
      CDSListField_Grantinvestment_Id( '');
      CDSListField_Grantinvestment_Type( '');
      except
        ExceptionProc( 'DoSetGrantInvestmentCode');
    end;
end;


{~DoResetGrantInvestment
  сброс полей описания кода субсидии}
procedure DoResetGrantInvestment;//( cds: TClientDataSet);
begin
    try
      DoSetGrantInvestmentCode( '');
      except
        ExceptionProc( 'DoResetGrantInvestment');
    end;
end;

{~DoUpdateGrantInvestmentType
  обновляет поля типа (Grantinvestment_Type) и кода (GrantInvestment_Code)
  в записи по идентификатору Grantinvestment_Id из справочника "субсидии и
  инвестиции".}
procedure DoUpdateGrantInvestmentType;//( cds: TClientDataSet);
var
  xXml: Variant;
  xRootXml: Variant;
begin
    try
      xXml := CreateOleObject('Microsoft.XmlDom');
      try
        Query.AddStr( CDSListField_Grantinvestment_Id_Get);
        xXml.LoadXML(
            Query.Run(
                 ' Select G.Code, G.FType From Grantinvestment G where g.id = ?'));
        xRootXml := xXml.documentElement;
        if (xRootXml.childNodes.Length = 1) then begin
          CDSListField_Grantinvestment_Code(
                              xRootXml.childNodes.Item[0].getAttribute('CODE'));
          CDSListField_Grantinvestment_Type(
                              xRootXml.childNodes.Item[0].getAttribute('FTYPE'));
        end
        finally
          xXml := NIL;
      end;
      except
        ExceptionProc( 'DoUpdateGrantInvestmentType');
    end;
end;


{~DoGrantInvestmentSetID
  подставляем идентификатор кода субсидии (из справочника) и дополняем
  описание кодасубсидии типом и кодом субсидии (по идентификатору из
  справочника "субсидии и инвестиции"}
procedure DoGrantInvestmentSetID( {cds: TClientDataSet;} aID: string);
begin
    try
      CDSListField_Grantinvestment_Id( aID);
      DoUpdateGrantInvestmentType;//( cds);
      except
        ExceptionProc( 'DoGrantInvestmentSetID');
    end;
end;


{~DoGrantInvestmentSetSystem
  подставляем описание системного кода субсидии и инвестиции ("не указан")}
procedure DoGrantInvestmentSetSystem;//( cds: TClientDataSet);
begin
  try
    DoGrantInvestmentSetID( {cds, }vGrantInvestmentID_SystemNull);
    except
      raise ('Сбой DoGrantInvestmentSetSystem: '+ LastException.Message);
  end;
end;


{~DoGrantInvestmentValidate
  проверяем правильность указания кода.
  возвращает =true если код не распознан. в этом случае ЭД  нужно установить
  на невыясненный.

  если код субсидии не распознан как системный то нужно заполнить поле
GrantInvestment_Id. для этого нужно найти запись в справочнике
"субсидии и инвестиции" по коду + организации + бюджет.
если найдено несколько записей - ставим ЭД на невыясненные ФТ-0.7
если код системный или распознать код удалось - обновляем поле типа
кода из справочника "субсидии и инвестиции" по идентификатору.

  параметры:
    aWithOrgNull
        =TRUE то допускается указание организации как "не указана" (Org_ID=0)
              но при этом код субсидии должен быть обязателен.
        =FALSE - проверяется только указание кода организации и кода субсидии.}
        {
function DoGrantInvestmentValidate( cds: TClientDataSet;
                                    aWithOrgNull: boolean): boolean;
begin
  if DoGrantInvestmentCodeIsSystem( cds)
    then Result := FALSE
    else
      if (DoSearchGrantInvestmentCode( cds, aWithOrgNull) = 1) then begin
        DoUpdateGrantInvestmentType( cds);
        Result := FALSE;
      end else begin
        Result := True;
        CDSListField_Message( cds, 'Код субсидии: ' + cds.FieldByName('Grantinvestment_Code').AsString
          + ' не найден в базе данных');
      end;
end;
         }

function DoGrantInvestmentValidate(// cds: TClientDataSet;
                                    aWithOrgNull: boolean = FALSE): boolean;
var
  s: string;
begin
    try
      s := CDSListField_Grantinvestment_Code_Get;//cds.FieldByName('GrantInvestment_Code').AsString;
      if (s = '') then begin
        // подставляем описание системного кода субсидии и инвестиции
        DoGrantInvestmentSetSystem;//( cds);
        Result := FALSE;
      end else
        if (s = FormatKbk( 'GRANTINVESTMENT',
                          '0',
                          CDSListField_Budget_Id_Get,//cds.FieldByName('Budget_Id').AsString,
                          0)) then begin
          // подставляем описание системного кода субсидии и инвестиции
          DoGrantInvestmentSetSystem;//( cds);
          Result := FALSE;
        end else begin
          // получаем код субсидии из справочника
          if (DoSearchGrantInvestmentCode( {cds, }aWithOrgNull) = 1)
            then begin
              // найден в справочнике - дополняем поля типа (и кода) из справочника
              DoUpdateGrantInvestmentType{( cds)};
              Result := FALSE;
            end else begin
              CDSListField_Message( {cds, }
                  FORMAT( 'Код субсидии %s не найден в базе данных',
                          [ CDSListField_Grantinvestment_Code_Get]));
              Result := TRUE;
            end;
        end;
      except
        ExceptionProc( 'DoGrantInvestmentValidate');
    end;
end;

{~isSearchAccount
  Ищет Id л/с. По номеру счета ищем его вхождение
  в наименование получателя из выписки.
  Нашли - вернули True и Id записали в cds LS_ID, иначе вернули False.
  exchange #2488
  если не нашли - ищем в "назначении платежа"}
function isSearchAccount({cds: TClientDataSet;} aXml: Variant): Boolean;
begin
    try
      // ищем счет в наименование получателя из выписки.
      case isSearchAccountByField( {cds, }aXml, CDSListField_Org_Name_Get{cds.FieldByName( 'Org_Name').asString}) of
       '1': Result := TRUE;     // нашли однозначно

       // не нашли - пробуем искать в назначении платежа
       '0': if (isSearchAccountByField( //cds,
                                        aXml,
                                        CDSListField_Description_Get{cds.FieldByName( 'Description').asString}) = '1')
              then Result := TRUE     // нашли однозначно
              else Result := FALSE;     // не нашли или дубль

      else Result := FALSE;     // дубль
      end;//case//
      except
        ExceptionProc( 'isSearchAccount');
    end;
end;

{~isSearchAccountByField
  Ищет Id л/с. По номеру счета ищем его вхождение его в строке aFieldValue.
  результат:
    '0' - не нашли
    '1' - нашли один раз
    'X' - нашли несколько раз.
  если найден однозначно, в записи фиксируется ID и номер счета.
  если не найден - ид.счета - пустой.
  если найден несколько раз (дубль), поля ид. и номер счета очищаются.}
function isSearchAccountByField( //cds: TClientDataSet;
                                  aXml: Variant;
                                  aFieldValue: string): string;
var
  i: Integer;
  xXml: Variant;
  xXmlNode: Variant;
begin
    try
      Result := '0';
      CDSListField_LS_ID( NULL);
      //ищем в наименовании получателя из выписки
      xXml := aXml.documentElement;
      for i := 0 to xXml.ChildNodes.Length - 1 do begin
        xXmlNode := xXml.ChildNodes.Item[i];
        if (Pos( xXmlNode.getAttribute('ACCOUNT_NUMBER'), aFieldValue) > 0) then
          if (Result = '0') then begin
            Result := '1';
            CDSListField_LS_ID( xXmlNode.getAttribute('ID'));
            CDSListField_LS_Number( xXmlNode.getAttribute('ACCOUNT_NUMBER'));
          end else begin
            //нашелся дубль
            Result := 'X';
            CDSListField_LS_ID( NULL);
            CDSListField_LS_Number( '');
            Break;
          end;
      end;//for//
      except
        ExceptionProc( 'isSearchAccountByField');
    end;
end;

{~GetValueFromSysparam
  получает данные по кодам невыясненных поступлений из системных параметров
  например:
    aubu.unclassified.rules.1000000148}
function GetValueFromSysparam(param_name: String): Variant;
var
  xXml: Variant;
  xXmlNode: Variant;
begin
    try
      xXml := CreateOleObject('Microsoft.XmlDom');
      xXmlNode := xXml.appendChild( xXml.createElement('EXCH_UTILS_PROCESSOR'));
      xXmlNode.setAttribute('action', 'get_sys_param_value');
      xXmlNode := xXmlNode.appendChild( xXml.createElement( 'SYSPARAM'));
      xXmlNode.setAttribute('NAME', param_name);
      xXml.loadXml( ExecXml( xXml.xml));
      xXml := xXml.GetElementsByTagName('PARAM_VALUE');
      if (xXml.length = 1) then begin
        Result := CreateOleObject('Microsoft.XmlDom');
        Result.loadXml( xXml.items[0].firstChild.data);
      end else begin
        Result := null;
      end;
      except
        ExceptionProc( 'GetValueFromSysparam');
    end;
end;


{~SearchKeywordIntoDescription
  поиск ключевого слова aKeyword в строке aDescription.
  ключевое слово ищется регистро независимо.
  значение определяется сразу за ключевым словом через один! пробел и
  заканчивается первым пробелом.

  если aNeedDelimBeforeValue=true,  то после ключевого слова должен быть
  один пробел, потом идет подстрока значения ключевого слова до первого пробела.

  если aNeedDelimBeforeValue=false, то после ключевого слова допускается
  отстутствие пробелов (или один пробел). если пробелов нет, то значение
  должно идти сразу за ключевым словом.

  например:
    ключевое слово "отр.код"
    ...на отр.код 14860000000000000180 без НДС...
  в результате возвратится: "14860000000000000180"
  для таких строк результат поиска отрицательный:
    ...на отр.код14860000000000000180 без НДС...
    ...на отр.код  14860000000000000180 без НДС...
    ...на отр.к од 14860000000000000180 без НДС...

  результат:
    =true - ключевое слово найдено один раз.
            значение возвращается в параметре aValue.
    =false - ключевое слово найдено несколько раз или не найдено ни разу.
            значение в aValue - возвращается пустая строка.}
function SearchKeywordIntoDescription( aDescription,aKeyword: String;
                                       aNeedDelimBeforeValue: boolean;
                                       var aValue: string): Boolean;
const
  // разделители: пробел и табуляция
  cnDelimiters = ' '#9;
var
  xPos,n, xLength: Integer;
  xDescription: string;
begin
    try
      Result := False;
      aValue := '';
      xLength := Length(aDescription);
      aKeyword := LowerCase(aKeyword);
      xDescription := LowerCase(aDescription);
      xPos := Pos(aKeyword, xDescription);
      if (xPos <= 0) then EXIT;
      // проверяем, есть ли такое слово дольше по тексту.
      // если есть - считаем что лексема не найдена.
      if (Pos(aKeyword, Copy(xDescription, xPos + 1, xLength - xPos)) > 0) then Exit;
      //увеличиваем индекс на длину ключевого слова
      INC( xPos, Length(aKeyword));
      //между ключевым словом и кодом должен быть пробел (и может не один)
      // ищем начало слова
      n := 0;
      while (xPos <= xLength) do begin
        if (POS( aDescription[xPos], cnDelimiters) <= 0) then BREAK;
        INC( xPos);
        INC( n);
      end;//while//
      if NOT(aNeedDelimBeforeValue) then begin
        // если указано больше одного пробела - результат отрицательный
        // допускается отсутствие пробелов после ключевого слова.
        if (n > 1) then EXIT;
      end else begin
        // если нет ни одного пробела или больше одного - результат отрицательный
        if (n <> 1) then EXIT;
      end;
      // если конец строки, значит значения нет - результат отрицательный
      if (xPos > xLength) then EXIT;
      // дальше получаем лексему до разделителя
      repeat
        aValue := aValue + aDescription[xPos];
        INC( xPos);
        if (xPos > xLength) then BREAK;
        if (POS( aDescription[xPos], cnDelimiters) > 0)
          then BREAK;
      until FALSE;
      Result := (aValue <> '');
      except
        ExceptionProc( 'SearchKeywordIntoDescription');
    end;
end;


{~SearchGrantInvestmentCode2709
  поиск ключевого слова aKeyword в строке aDescription.
  ключевое слово ищется регистро (не)зависимо - определяется переданным параметром aCaseSensitive
  значение определяется сразу за ключевым словом через один! пробел и
  заканчивается первым пробелом.

  если aNeedDelimBeforeValue=true,  то после ключевого слова должен быть
  один пробел, потом идет подстрока значения ключевого слова до первого пробела.

  если aNeedDelimBeforeValue=false, то после ключевого слова допускается
  отстутствие пробелов (или один пробел). если пробелов нет, то значение
  должно идти сразу за ключевым словом.

  например:
    ключевое слово "отр.код"
    ...на отр.код 14860000000000000180 без НДС...
  в результате возвратится: "14860000000000000180"
  для таких строк результат поиска отрицательный:
    ...на отр.код14860000000000000180 без НДС...
    ...на отр.код  14860000000000000180 без НДС...
    ...на отр.к од 14860000000000000180 без НДС...

  результат:
    =true - ключевое слово найдено один раз.
            значение возвращается в параметре aValue.
    =false - ключевое слово найдено несколько раз или не найдено ни разу.
            значение в aValue - возвращается пустая строка.

   exchange #2709 - дорабатывается разбор кода субсидии
   Искать символы «Код субсидии», без учета регистра, в Назначении платежа,
  после которых через пробел определять сам код, в соответствии
  со справочником «Виды классификаторов» по длине маски для строки
  с заголовком «Коды субсидий».
   добавлен параметр:
      aCodeNULL - нулевой код (по маске).
      одидается что код субсидии должен быть длиной, равный этому коду.}
function SearchGrantInvestmentCode2709( aDescription: String;
                                        aKeyword: String;
                                        aNeedDelimBeforeValue: boolean;
                                        aCodeNULL: String;
                                        aCaseSensitive: boolean;
                                        var aValue: string): Boolean;
const
  // разделители: пробел и табуляция
  cnDelimiters = ' '#9;
var
  xPos,n, xLength: Integer;
  xDescription: string;
begin
    try
      Result := False;
      aValue := '';
      xLength := Length(aDescription);
      xDescription := aDescription;
      if aCaseSensitive then
      begin
        aKeyword := LowerCase(aKeyword);
        xDescription := LowerCase(aDescription);
      end;
      xPos := Pos(aKeyword, xDescription);
      if (xPos <= 0) then EXIT;
      // проверяем, есть ли такое слово дольше по тексту.
      // если есть - считаем что лексема не найдена.
      if (Pos(aKeyword, Copy(xDescription, xPos + 1, xLength - xPos)) > 0) then Exit;
      //увеличиваем индекс на длину ключевого слова
      INC( xPos, Length(aKeyword));
      //между ключевым словом и кодом должен быть пробел (и может не один)
      // ищем начало слова
      n := 0;
      while (xPos <= xLength) do begin
        if (POS( aDescription[xPos], cnDelimiters) <= 0) then BREAK;
        INC( xPos);
        INC( n);
      end;//while//
      if NOT(aNeedDelimBeforeValue) then begin
        // если указано больше одного пробела - результат отрицательный
        // допускается отсутствие пробелов после ключевого слова.
        if (n > 1) then EXIT;
      end else begin
        // если нет ни одного пробела или больше одного - результат отрицательный
        if (n <> 1) then EXIT;
      end;
      // если конец строки, значит значения нет - результат отрицательный
      if (xPos > xLength) then EXIT;
      // дальше получаем лексему кода.
      repeat
        aValue := aValue + aDescription[xPos];
        // длина кода ожидается равной длине нулевого кода из параметра aCodeNULL.
        // при этом разделители не учитываются - строго длина строки кода
        // равная длине кода по маске.
        if (LENGTH( aValue) = LENGTH( aCodeNULL)) then begin
          Result := TRUE;
          BREAK;
        end;
        INC( xPos);
        // если неожиданно будет конец строки, то прерываем разбор,
        // а результат будет отрицательным.
        if (xPos > xLength) then BREAK;
      until FALSE;
      except
        ExceptionProc( 'SearchGrantInvestmentCode2709');
    end;
end;


{~ParceDescription
  поиск ключевого слова aKeyword в строке aDescription.
  ключевое слово ищется регистро независимо.
  значение определяется сразу за ключевым словом через один! пробел и
  заканчивается первым пробелом.
  например:
    ключевое слово "отр.код"
    ...на отр.код 14860000000000000180 без НДС...
  в результате возвратится: "14860000000000000180"
  для таких строк результат поиска отрицательный:
    ...на отр.код14860000000000000180 без НДС...
    ...на отр.код  14860000000000000180 без НДС...
    ...на отр.к од 14860000000000000180 без НДС...

  результат:
    =true - ключевое слово найдено один раз.
            значение возвращается в параметре aValue.
    =false - ключевое слово найдено несколько раз или не найдено ни разу.
            значение в aValue - возвращается пустая строка.

  для ключевого слова "отр.код", если такое солово не будет найдено, будет
  выполнена найти слово: "отр. код".

  exchange #2709 и #2708
  доработка поиска кода субсидии.
  если в параметре aKeyword будут переданы строки '#2709' или '#2708',
  то будет выполнен поиск кода субсидии по новому алгоритму.}
function ParceDescription(  aDescription,aKeyword: String;
                            aNeedDelimBeforeValue: boolean;
                            var aValue: string;
                            aCodeNULL: string): Boolean;
var
  xSL: TStringList;
  i: integer;
  xIsResult: Boolean;     // из-за странностей эксча будет избыточный код
begin
    try
      // exchange #2709 и #2708
      // доработка поиска кода субсидии
      if (aKeyword in ['#2709','#2708']) then
      begin
        xSL := TStringList.Create;
        try
          // Exchange, #3215
          Result := false;
          xSL.CommaText := vCfg_FindGrInvCodeBySymbols;
          for i:=0 to xSL.Count -1 do
          begin
            // ищем код субсидии по новому правилу
            xIsResult := SearchGrantInvestmentCode2709(
                                    aDescription,
                                    TRIM(xSL[i]),
                                    aNeedDelimBeforeValue,
                                    aCodeNULL,
                                    false,
                                    aValue);
            if xIsResult then
              break;
          end;
        finally
          xSL.Free;
        end;
        Result := xIsResult;
        EXIT;
      end;

      Result := SearchKeywordIntoDescription( aDescription,
                                              aKeyword,
                                              aNeedDelimBeforeValue,
                                              aValue);
      if NOT Result then
        case aKeyword of
          'отр.код':
              Result := SearchKeywordIntoDescription( aDescription,
                                                      'отр. код',
                                                      aNeedDelimBeforeValue,
                                                      aValue);
        end;//case//
      except
        ExceptionProc( 'ParceDescription');
    end;
end;


{~ParceDescriptionCDS
  поиск ключевого слова aKeyword в поле aDescription набора данных aCDS.
  ключевое слово ищется регистро независимо.
  значение определяется сразу за ключевым словом через один! пробел и
  заканчивается первым пробелом.
  например:
    ключевое слово "отр.код"
    ...на отр.код 14860000000000000180 без НДС...
  в результате возвратится: "14860000000000000180"
  для таких строк результат поиска отрицательный:
    ...на отр.код14860000000000000180 без НДС...
    ...на отр.код  14860000000000000180 без НДС...
    ...на отр.к од 14860000000000000180 без НДС...

  результат:
    =true - ключевое слово найдено один раз.
            значение возвращается в параметре aValue.
    =false - ключевое слово найдено несколько раз или не найдено ни разу.
            значение в aValue - возвращается пустая строка.

  для ключевого слова "отр.код", если такое солово не будет найдено, будет
  выполнена найти слово: "отр. код".}
function ParceDescriptionCDS( //aCDS: TClientDataSet;
                              aKeyword: String;
                              aMustBeSpace: Boolean;
                              aCodeNULL: string = ''): Boolean;
var xCode: String;
begin
    try
      Result := ParceDescription( CDSListField_Description_Get, //aCDS.FieldByName('Description').asString,
                                  aKeyword,
                                  aMustBeSpace,
                                  xCode,
                                  aCodeNULL);
      if Result then
        case aKeyword of
          // exchange #2709 и #2708
          // доработка поиска кода субсидии
          '#2709',
          '#2708',
          'код субсидии': begin
              DoSetGrantInvestmentCode( {aCDS, }xCode);
            end;

          'отр.код': begin
              CDSListField_Industry_Code( Copy(xCode, 1, 17));
              CDSListField_Kesr_Code( Copy(xCode, 18, 3));
            end;

          'косгу': begin
              CDSListField_Kesr_Code( Copy(xCode, 1, 3));
            end;

          'ан.группа', 'ан. группа': begin
              CDSListField_Ksda_Code( Copy(xCode, 1, 3));
            end;
        end;//case//
      except
        ExceptionProc( 'ParceDescriptionCDS');
    end;
end;


{~CheckKES
  проверка поля КОСГУ: cds.FieldByName('Kesr_Code')

  здесь проверяется код на =0 (с преобразованием в число) и поиск кода в БД.
  если изначально код косгу не указан, то он заменяется на aKES_Default,
  а затем проверяется как косгу.
  по умолчанию, если косгу не указан, он заменяется на системный код 0.

  exchange #2627
  всвязи с переходом на символьные код КОСГУ не будет числовым, поэтому проверка
  на число отменяется. но при этом необходимо проверить код на =0.
  поэтому преобразование в число остается, но допускается, что код
  может быть не числовым.

  результат:
    =true - код найден в бд.
    =false - код не найден в бд.
              при этом пишется сообщение: КОСГУ: ххх не найден в бд.}
function CheckKES( aKES_Default: string = '0'): boolean;
var
  xCode: string;
  xBudgetID: string;
  xcodeNull: string;
begin
    try
      Result := FALSE;

      xCode := StringReplace( CDSListField_Kesr_Code_Get, ' ', '');
      if (LENGTH( xCode) <= 0) then
        if (LENGTH( aKES_Default) <= 0) then begin
          CDSListField_Message( {aCDS,} 'КОСГУ: не указан.');
          EXIT;
        end else begin
          // заменяем пустой косгу на указанный
          xCode := aKES_Default;
          CDSListField_Kesr_Code( aKES_Default);
        end;

      xBudgetID := CDSListField_Budget_Id_Get;//aCDS.FieldByName('Budget_Id').AsString;

      // получаем системный нулевой код КОСГУ
      xCodeNull := FormatKbk( 'KES', '0', xBudgetID);

      // если длина кода =1 и сам код указан =0, то будем искать системный код
      if ((LENGTH( xCode) = 1) AND (xCode = '0')) then begin
        // подставляем указанный код
        CDSListField_Kesr_Code( xCodeNull);
        Result := TRUE;
        EXIT;
      end;

      // если длину КОСГУ не совпадает с длиной системного кода
      // т.е. оджидаемой длиной кода, то это ошибка.
      // его даже в бд не ищем.
      if (LENGTH( xCode) <> LENGTH( xCodeNull)) then begin
        CDSListField_Message( FORMAT( 'КОСГУ: %s указан не верно.', [ xCode]));
        EXIT;
      end;

      //ищет КОСГУ в базе
      if (QueryField( 'Select count( * ) N From Kes Where Budget_Id = ? and Code = ?',
                      [ xBudgetID,
                        xCode],
                        'N') <> '1')
        then begin
          CDSListField_Message( FORMAT( 'КОСГУ: %s не найден в базе данных.', [ xCode]));
          EXIT;
        end;

      Result := TRUE;
      except
        ExceptionProc( 'CheckKES');
    end;
end;


{~SetNullIndustryCode
  поиск и подстановка нулевого отр.кода.
  результат:
    =true - код найден и сохранен.
    =false - 0-й код не найден в бд (не найден ID),
              но подставлен 0-й код по маске в поле кода.}
function SetNullIndustryCode{( aCDS: TClientDataSet)}: boolean;
begin
    try
      CDSListField_Industry_Code(
                GetMaskIndustryCode( '0', CDSListField_Budget_Id_Get));
      // ищем код в объединенном запросе
      Result := CheckIndustryCode_( {aCDS, }FALSE);
      except
        ExceptionProc( 'SetNullIndustryCode');
    end;
end;


{~CheckIndustryCode_
  exchange #2667 и #2666 при проверке отр.кода проверять не объединение
  подзапросов а отдельно: сначала найти для организации, а если не найден
  проверить системный отр.код (но не оба одновременно).
  когда проверяются одновременно оба подзапроса возвращаются две записи,
  что при водит к ошибке поиска.
}
function CheckIndustryCode_( //cds: TClientDataSet;
                              aDivide: boolean): boolean;

var
  xXML: Variant;
  sql: string;

  procedure DoError_;
  begin
    CDSListField_Message( {cds, }'Отраслевой код ' + CDSListField_Industry_Code_Get
      + ' не найден в базе');
  end;

  procedure DoSaveCode_;
  begin
    CDSListField_Industrycode_Id(
                    xXml.documentElement.childNodes.Item[0].getAttribute('ID'));
  end;

begin
    try
      xXml := CreateOleObject('Microsoft.XmlDom');

      if aDivide then begin
        sql := 'Select I.Id' +
          ' From IndustryCode I' +
          ' Join Org O on O.Manager_Id = I.Org_id' +
          ' Where I.Budget_Id = ? and O.Id = ? and I.Code = ?';
        Query.AddStr( CDSListField_Budget_Id_Get);//cds.FieldByName('Budget_Id').AsString);
        Query.AddStr( CDSListField_Org_Id_Get);//cds.FieldByName('Org_Id').AsString);
        Query.AddStr( CDSListField_Industry_Code_Get);
        xXml.LoadXML( Query.Run(sql));

        if xXml.documentElement.childNodes.Length = 1 then begin
          DoSaveCode_;
          Result := TRUE;
          EXIT;
        end;

        // если найдено несколько кодов, искать еще и вместе с объединенным
        // запросом безсмысленно.
        if xXml.documentElement.childNodes.Length > 1 then begin
          DoError_;
          Result := FALSE;
          EXIT;
        end;
      end;

      // запрос по старому
      sql := 'Select I.Id' +
        ' From IndustryCode I' +
        ' Join Org O on O.Manager_Id = I.Org_id' +
        ' Where I.Budget_Id = ? and O.Id = ? and I.Code = ?' +
        ' Union Select I.Id' +
        ' From IndustryCode I' +
        ' Where I.Budget_Id = ? and I.Org_Id = 0 and I.Code = ?';
      Query.AddStr( CDSListField_Budget_Id_Get);//cds.FieldByName('Budget_Id').AsString);
      Query.AddStr( CDSListField_Org_Id_Get);
      Query.AddStr( CDSListField_Industry_Code_Get);
      Query.AddStr( CDSListField_Budget_Id_Get);//cds.FieldByName('Budget_Id').AsString);
      Query.AddStr( CDSListField_Industry_Code_Get);
      // exchange #3036
      xXml.LoadXML(Query.Run(sql));
      if xXml.documentElement.childNodes.Length = 1 then begin
        DoSaveCode_;
        Result := TRUE;
      end else begin
        DoError_;
        Result := FALSE;
      end;
      except
        ExceptionProc( 'CheckIndustryCode_');
    end;
end;

function CheckIndustryCode{( aCDS: TClientDataSet)}: boolean;
begin
    try
      Result := CheckIndustryCode_( {aCDS, }TRUE);
      except
        ExceptionProc( 'CheckIndustryCode');
    end;
end;


{~BooleanAsString
  макрос возвращает строковое значпредставление булевого значения aValue в
  виде TRUE или FALSE. }
function BooleanAsString( aValue: boolean): string;
begin
  if aValue then Result := 'TRUE' else Result := 'FALSE';
end;



{~XmlAttrExists
  поиск атрибута xAttrName в узле XML: xNode.
  если xNonRegister:
     =false выполняется проверка имени атрибута как указано в xAttrName;
     =true - проверка атрибута без учета регистра. это не совсем корректно,
             поскольку обращение к атрибутам по имени регистрозависимо.
             это удобно использовать для поиска вообще атрибута, если
             точное имя либо не известно, либо нужно просто проверить
             наличие такого атрибута.
             крайне нежелательно указывать =true, если нужно проверить
             атрибут при последующем чтении, если такой атрибут есть.
  возвращает:
     =true если атрибут xAttrName присутствует в узле xXML.
     =false - нет атрибута в узле. }
function XmlAttrExists( xNode: variant; xAttrName: string; xNonRegister: boolean = FALSE): boolean;
var
  xAttrList: variant;
  i: integer;
  s: string;
begin
  try
    Result := FALSE;
    if VarIsNull( xNode) then Exit;
    xAttrList := xNode.attributes;
    if VarIsNull( xAttrList) then Exit;
    if xNonRegister then begin
      s := UpperCase( xAttrName);
      for i := 0 to xAttrList.length - 1 do
    	  if (UpperCase( xAttrList.item(i).NodeName) = s) then begin
    	    Result := TRUE;
    	    Exit;
    	  end;
    end else begin
      for i := 0 to xAttrList.length - 1 do
    	  if (xAttrList.item(i).NodeName = xAttrName) then begin
    	    Result := TRUE;
    	    Exit;
    	  end;
    end;
    except
      ExceptionLog( 'Сбой скрипта: "Un_ToolsXML.XmlAttrExists".');
  end;
end;

{~GetNodeAttributeEx
  специальная функция получения значения атрибута.
  фактически она заменяет выражение: VarToStr( aNode.GetAttribute( aAttrName))
  но так удобно отлаживать скрипт.}
function GetNodeAttributeEx( aNode: Variant;
                             aAttrName: string): string;
begin
    try
      Result := VarToStr( aNode.GetAttribute( aAttrName));
      except
        ExceptionLog( 'Сбой GetNodeAttributeEx');
    end;
end;

{********************* Доп. функции работы с КБК ******************************}
//проверка нахождения кода в базе.
function KbkExists(BudgetId, Code, Table: String): Boolean;
var
  sql: String;
begin
  
  Result := False;
  
  if Code <> '' then
  begin
    sql := 'Select count(*) N From ' + Table + ' Where Budget_Id = ? and Code = ?';
    if (QueryField(sql, [BudgetId, Code], 'N') = '1') then
      Result := TRUE;
  end;
end;


function SearchUnicalKVRCode(aBudgetID, aIndustryCodeID, aGrantinvestmentID: string;
                                           aKESR, aFSR, aPayID: string;
                                           aFillKES: boolean): string;
var
  xXml: Variant;
begin
  try
    if aFillKES then
    xXml := QueryXml(
                         ' select distinct aubub.kvr_code KVR_CODE '
                        +' from aububudget aubub '
                        +' where aubub.budget_id=? and aubub.industrycode_id = ? '
                        +' and aubub.grantinvestment_id = ? and kesr_code = ? '
                        +'  and aubub.fsr_id=? and aubub.pay_id=? ',
                        [ aBudgetID,
                          aIndustryCodeID,
                          aGrantinvestmentID,
                          aKESR,
                          aFSR,
                          aPayID
                          ])
    else xXml := QueryXml(
                         ' select distinct aubub.kvr_code KVR_CODE '
                        +' from aububudget aubub '
                        +' where aubub.budget_id=? and aubub.industrycode_id = ? '
                        +' and aubub.grantinvestment_id = ? '
                        +'  and aubub.fsr_id=? and aubub.pay_id=? ',
                        [ aBudgetID,
                          aIndustryCodeID,
                          aGrantinvestmentID,
                          aFSR,
                          aPayID
                          ]);
      if (xXml.documentElement.childNodes.Length <> 1) then
        Result := ''
      else
        Result := xXml.documentElement.childNodes.Item[0].GetAttribute( 'KVR_CODE');
  except
    ExceptionLog( 'Сбой SearchUnicalKVRCode');
  end;
end;

function SysParamBudgetValue(budgetId, pageName, paramName: String): string;
var
  XmlDoc: Variant;
begin
  try
    XmlDoc := CreateOleObject('Microsoft.XmlDom');
    XmlDoc.LoadXML('<EXCH_UTILS_PROCESSOR action="get_budget_param_value" ' +
      ' BUDGET_ID="' + budgetId + '" PAGE_NAME="' + pageName + '" PARAM_NAME="' + paramName + '"/>');
    XmlDoc.LoadXML(ExecXml(XmlDoc.xml));
    Result := XmlDoc.documentElement.getAttribute('PARAM_VALUE');
  except
    ExceptionProc('Сбой Un_Tools.SysParamBudgetValue');
  end;
end;



begin
end.
