{
     подпрограммы для поддержки версий.
     в основном предназначалась для проверки версий ТФФ, но затем в модуль
     добавлены функции проверки версий АЦК-финансы и АЦК-exchange.
     особенность в том, что в режиме отладки допускается блокирование проверок
     версий, без правки кода!

     Создан по записи: Exchange #2336
}
{не удалять эту строку
uses
  'Un_Schems',
  'Un_ToolsXml',
  'Un_ConstRef',
  'Un_Progress',
  'Un_System',
  'Un_Log',
  'Un_Debug',
  'Un_ProgressControls';
{не удалять эту строку}

const
  // текущая версия ТФФ по умолчанию (такая же версия должна быть
  // в конфигурационных для выбора последней версии ТФФ).
  // exchange #2393: Дата вступления в действие ТФФ 10.0 - 01.01.2013.
  // exchange #2542: Дата вступления в действие ТФФ 11.0 - 01.07.2013.
  // exchange #2593: Дата вступления в действие ТФФ 11.2 - 21.10.2013.
  // exchange #2616: Дата вступления в действие ТФФ 12 - 01.01.2014.
  // exchange #2772: Дата вступления в действие ТФФ 13 - 01.07.2014.
  // exchange #2795: Дата вступления в действие ТФФ 14 - 01.10.2014.
  // exchange #2823: Дата вступления в действие ТФФ 15 - 01.01.2015.
  // exchange #2855: Дата вступления в действие ТФФ 16 - 01.01.2015.
  // exchange #2934: Дата вступления в действие ТФФ 17 - 31.03.2015.
  // exchange #2996: Дата вступления в действие ТФФ 18 - 01.07.2015.
  // exchange #3040: Дата вступления в действие ТФФ 19 - 28.09.2015.
  // exchange #3078: Дата вступления в действие ТФФ 20 - 01.01.2016.
  // exchange #3147: Дата вступления в действие ТФФ 20.2 - 01.01.2016.
  // АЦК-Ф, #22954 : Дата вступления в действие ТФФ 20.3 - 28.03.2016.
  // Exchange, #3163:Дата вступления в действие ТФФ 21.0 - 13.05.2016.
  // Exchange, #3270:Дата вступления в действие ТФФ 22.0 - 01.01.2017.
  cnCurrentVersionTFF = '22.0';

  // совместимость с версией ТФФ11
  // тут нужно указывать все комбинации версий через запятую, с которыми
  // требуется проверить совместимость. для проверки последней версии ТФФ,
  // константа должна содержать значение из константы cnCurrentVersionTFF.
  // константа предназначена для функции IsVersionSupport.
  // Exchange, #3270  - переход наТФФ 22.0. прежняя схема: 21.0
  cnVersionTFF_22 = '22,22.0';
  // Exchange, #3163  - переход наТФФ 21.0. прежняя схема: 20.3
  cnVersionTFF_21 = '21,21.0';
  // АЦК-Ф, #22954  - переход наТФФ 20.3. прежняя схема: 20.2
  cnVersionTFF_20_3 = '20.3';
  // exchange #3147 - переход наТФФ 20.2. прежняя схема: 20.0
  cnVersionTFF_20_2 = '20.2';
  // exchange #3078 - переход наТФФ 20. прежняя схема: 19.0
  cnVersionTFF_20 = '20,20.0';
  // exchange #3040 - переход наТФФ 19. прежняя схема: 18.0
  cnVersionTFF_19 = '19,19.0';
  // exchange #2996 - переход наТФФ 18. прежняя схема: 17.0
  cnVersionTFF_18 = '18,18.0';
  // exchange #2934 - переход наТФФ 17. прежняя схема: 16.0
  cnVersionTFF_17 = '17,17.0';
  // exchange #2855 - переход наТФФ 16. прежняя схема: 15.0
  cnVersionTFF_16 = '16,16.0';
  // exchange #2823 - переход наТФФ 15. прежняя схема: 14.0
  cnVersionTFF_15 = '15,15.0';
  // exchange #2795 - переход наТФФ 14. прежняя схема: 13.0
  cnVersionTFF_14 = '14,14.0';
  // exchange #2772 - переход наТФФ 13. прежняя схема: 12.2
  cnVersionTFF_13 = '13,13.0';
  cnVersionTFF_12_2 = '12.2';
  // exchange #2727
  // добавлена доп.схема 12.2.1 для выгрузки в поле UIN значения 0.
  cnVersionTFF_12_2_1 = '12.2.1';
//  cnVersionTFF_11 = '11';
//  cnVersionTFF_11 = '11,11.0';
//  cnVersionTFF_11_2 = '11.2';
//  cnVersionTFF_12 = '12,12.0';

var
  // текущая версия (выбранная в конфигурации).
  vVersionTFF: string;
  // список файлов схем текущей версии ТФФ
//  vVersionTFF_Files: TStringList;
  // корневой каталог файлов текущей схемы ТФФ
  // в конце строки должен быть слеш!
  vVersionTFF_Dir: string;


{~CompareVersionAZK_Finance
  проверка версии aVersion АЦК-Финансы для выполнения операции aOperation.
  операция допускается, если текущая версия АЦК-Финансы больше или равна
  указанной версии.
  если проверка версии не пройдена выполняется остановка скрипта, если
  параметр aHalt=true, или возвращается =false без остановки скрипта, если
  aHalt=false.
  остановка допускается только в новых версиях AzkExchange, поэтому
  остановка дублируется результатом:
    =true - проврка пройдена успешно (версия АЦК-финансы >= aVersion);
    =false - проверка не пройдена.
  проверку можно блокировать отладочной опцией.
  параметр halt больше не используется. вместо этого используется raise}
function CompareVersionAZK_Finance( aOperation, aVersion: string;
                                    aHalt: boolean = FALSE;
                                    aShowMsg: boolean = FALSE;
                                    aMaska: integer = 2): boolean;
begin
 // Начиная с версии 2.41 в сравнении нет необходимости, т.к. эксч теперь нумеруется согласно нумерации АЦК
 Result := true;
   { try
      Result := FALSE;

      // проверяем необходимую версию АЦК Финансы
      // exchange #2472
      // здесь проверяем только старшие группы версии (x.x) остальные не проверяем.
      if (CompareVersionEx( AzkVersion, aVersion, aMaska) < 0) then begin
        WriteInfo( FORMAT(  '%s работает начиная с версии АЦК-Финансы %s. '
                           +'Текущая версия АЦК-Финансы %s.',
                           [ aOperation,
                             aVersion,
                             AzkVersion]));
        if aHalt
          then raise( 'Прерывание скрипта.');
        if aShowMsg
          then MessageDlg( FORMAT( '%s работает начиная с версии АЦК-Финансы %s. '
                             +'Текущая версия АЦК-Финансы %s.',
                             [ aOperation,
                               aVersion,
                               AzkVersion]),
                           mtConfirmation,
                           mbCancel,
                           0);
        EXIT;
      end;
      Result := TRUE;
      except
        ExceptionProc( 'CompareVersionAZK_Finance');
    end;     }
end;

{~CompareVersionAZK_Exchange
 проверка версии aVersion АЦК-Exchange для выполнения операции aOperation.
  операция допускается, если текущая версия АЦК-Exchange больше или равна
  указанной версии.
  если проверка версии не пройдена выполняется остановка скрипта, если
  параметр aHalt=true, или возвращается =false без остановки скрипта, если
  aHalt=false.
  остановка допускается только в новых версиях AzkExchange, поэтому
  остановка дублируется результатом:
    =true - проврка пройдена успешно (версия АЦК-Exchange >= aVersion);
    =false - проверка не пройдена.
  проверку можно блокировать отладочной опцией. }
function CompareVersionAZK_Exchange( aOperation, aVersion: string;
                                     aHalt: boolean = FALSE;
                                    aShowMsg: boolean = FALSE;
                                    aMaska: integer = 0): boolean;
begin
 // Начиная с версии 2.41 в сравнении нет необходимости, т.к. эксч и скрипты нумеруются согласно нумерации АЦК
 Result := true;
{
  Result := FALSE;
    try
      // проверяем необходимую версию АЦК Финансы
      // здесь проверка выполняется полностью (все группы цифр в версии)
      if (CompareVersionEx( AppVersion, aVersion, aMaska) < 0) then begin
        WriteInfo( FORMAT(  '%s работает начиная с версии АЦК Exchange %s. '
                           +'Текущая версия АЦК Exchange  %s.',
                           [ aOperation,
                             aVersion,
                             AppVersion]));
        if aHalt
          then raise( 'Прерывание скрипта.');
        if aShowMsg
          then MessageDlg( FORMAT( '%s работает начиная с версии АЦК Exchange %s. '
                               +'Текущая версия АЦК Exchange  %s.',
                               [ aOperation,
                                 aVersion,
                                 AppVersion]),
                           mtConfirmation,
                           mbCancel,
                           0);
        EXIT;
      end;
      Result := TRUE;
      except
        ExceptionProc( 'CompareVersionAZK_Exchange');
    end;  }
end;


{~CompareVersionEx
  сравнение версий с учетом отладки.
  если включена отладочная опция "не проверять версии" возвращается всегда
  совпадение версий.
  результат:
    -1: xVer1 < xVer2;
     0: xVer1 = xVer2;
     1: xVer1 > xVer2;

  маска добавлена по #2472.
  идея в том, чтобы не проверять полностью номера версий, а только значимые
  (необходимые) группы. маска указывает, сколько групп нужно проверить.
  маска указывает, сколько групп (слева - от старших) нужно использовать для
  проверки, а прочие группы будут отсечены от строк версии и не проверяются.
  если aMaska <=0 то проверяюся все группы (как раньше).
  по умолчанию, проверка работает как раньше (маска = 0)}
function CompareVersionEx( xVer1, xVer2: string; aMaska: integer = 0): integer;

  function DoMaskedVersion( aVersion: string): string;
  var
    i, n: integer;
  begin
  
      try
        if (aMaska > 0) then begin
          n := 1;
          for i := 1 to LENGTH( aVersion) do
            if (POS( aVersion[i], '1234567890') <= 0) then begin
              INC( n);
              if (n > aMaska) then begin
                Result := COPY( aVersion, 1, i - 1);
                EXIT;
              end;
            end;

        end;
        Result := aVersion;
        except
          ExceptionLog( 'Сбой Un_VersionSupport.DoMaskedVersion');
      end;
  
  end;

begin

    try
      Result := CompareVersion( DoMaskedVersion( xVer1),
                                DoMaskedVersion( xVer2));
      except
        ExceptionLog( 'Сбой Un_VersionSupport.CompareVersionEx');
    end;

end;

{~CompareVersionTFF
  проверяет, является ли версия xCompareVersion в списке указанных в xVersions.
  если в списке xVersions есть версия xCompareVersion - возвращается =TRUE,
  иначе =FALSE. список версий перечисляется через запятую.
  если версия xCompareVersion не указана - возвращается =TRUE.
  если xVersions указан пустой строкой  - возвращается =TRUE.
  для проверки можно выполнить:
    xresult := CompareVersionTFF( cnVersionTFF_xxx, cnCurrentVersionTFF);
  результат всегда должен быть: xresult=TRUE, поскольку версия cnVersionTFF_xxx
  обязательно должна входить в текущую версию ТФФ cnCurrentVersionTFF
  (см. описание констант в этом модуле). }
function CompareVersionTFF( xVersions, xCompareVersion: string): boolean;
begin

    try
      // если список версий не указан - значит должно работать при любой версии
      if (Length( xVersions) <= 0)
        then Result := TRUE
        else
      // если текущая версия не указана - значит должно работать при любой версии
      if (Length( xCompareVersion) <= 0)
        then Result := TRUE
        else
      // либо указана только эта версия
      if ( xCompareVersion = xVersions)
        then Result := TRUE
        else
      // либо есть строка ",версия," в строке xVersions
      if (POS( ',' + xCompareVersion + ',', xVersions) > 0)
        then Result := TRUE
        else
      // либо есть строка "версия," в начале строки xVersions
      if (POS( xCompareVersion + ',', xVersions) = 1)
        then Result := TRUE
        else
      // либо есть строка ",версия" в конце строки xVersions
      if ((POS( ',' + xCompareVersion, xVersions) =
                 (LENGTH( xVersions) - LENGTH( xCompareVersion)))
          AND ((LENGTH( xVersions) - LENGTH( xCompareVersion)) > 0))
        then Result := TRUE
        else Result := FALSE;
      except
        ExceptionLog( 'Сбой Un_VersionSupport.CompareVersionTFF');
    end;

end;

{~IsVersionSupport
  проверяет, является ли текущая версия (vVersionTFF) в списке указанных в xVersions.
  если в списке xVersions есть версия vVersionTFF - возвращается =TRUE,
  иначе =FALSE. список версий перечисляется через запятую.
  если текущая версия (vVersionTFF) не указана - возвращается =TRUE.
  если xVersions указан пустой строкой  - возвращается =TRUE.
  в начале проверяется вхождение текущей версии (vVersionTFF) в xVersions.
  если результат отрицательный, проверяется:
    если строка xVersions указана и содержит символ ".", то возвращается =FALSE.
    tсли строка xVersions указана и не содержит символов ".", то к строке
      xVersions добавляется .0 и выполняется повторная проверка вхождение
      текущей версии в строку xVersions.}
function IsVersionSupport( xVersions: string): boolean;
begin

    try
      if CompareVersionTFF( xVersions, vVersionTFF)
        then Result := TRUE
        else
      if (LENGTH( xVersions) <= 0)
        then Result := FALSE
        else
      if (POS( '.', xVersions) > 0)
        then Result := FALSE
        else
      if CompareVersionTFF( xVersions + '.0', vVersionTFF)
        then Result := TRUE
        else Result := FALSE;
      except
        ExceptionLog( 'Сбой Un_VersionSupport.IsVersionSupport');
    end;

end;

{~CheckVersionSupportData
  сверяется версия данных (aData) с версией схемы.
  если номер версии не совпадает - возвращается ение =false. при этом сообщение
  об ошибке записывается в логе и нужно завершить обработку файла.
  если номер версии совпадает - возвращается значение =TRUE.
  в функции не проверяется секция Data что это: FK или другая секция.
  тут проверяется только атрибут NUM_VER. если секция не FK такого атрибута
  нет, и возвратится пустая строка.}
function CheckVersionSupportData( aData: variant): boolean;
var
  sVersion: string;
begin

    try
      if (Data.childNodes.length <= 0) then begin
        WriteInfo( 'Ошибка входных данных: нет ни одного раздела с данными.');
        Result := FALSE;
        EXIT;
      end;
      if (Data.childNodes.Item[0].NodeName <> 'FK') then begin
        WriteInfo( 'Ошибка входных данных: раздел FK должен быть первым во входном файле.');
        Result := FALSE;
        EXIT;
      end;
      sVersion := VarToStr( Data.childNodes.Item[0].getAttribute('NUM_VER'));
      Result   := (sVersion = vFormVer);
      if NOT Result
        then WriteInfo( FORMAT( 'Ошибка входных данных: версия формата: %s не поддерживается. Требуется версия файла = %s.',
                                [ sVersion, vFormVer]));
      except
        ExceptionProc( 'CheckVersionSupportData');
    end;

end;


{~ParseParametersVersionSupport
  получение значения опции поддержки версий из списка параметров xParams.
  вызывается из подпрограммы Un_Default.InitializeDefault (инициализация
  при запуске главных скриптов).
  параметры:
    aDirIni - корневой каталог скриптов.
              это нужно чтобы получить список файлов схем.
    aParams - конфигурационные параметры, секция ini.
              из этого набора будет взят параметр "Поддержка версии ТФФ".}
procedure ParseParametersVersionSupport( aDirIni: string;
                                        aParams: TStringList);
begin

    try
      ParseParameterVersionSupport( aDirIni,
                                    aParams.Values['Поддержка версии ТФФ']);
      except
        ExceptionLog( 'Сбой Un_VersionSupport.ParseParametersVersionSupport');
    end;

end;

{~ParseParameterVersionSupport
  выделение значения опции поддержки версий из параметра aParamTFF.
  помимо этого сохраняется строка с каталогом к схемам и загружается список
  файлов для текущей схемы ТФФ.
  это дает то, что в результате нужно не проверять текущую схему в каждом скрипте
  и не заботится о полных именах файлов, а указать для загрузки начало файла
  схемы, без даты.}
procedure ParseParameterVersionSupport( aDirIni: string;
                                       aParamTFF: string);
var
  xVersionDir: string;
begin

    try
//      Result := 0;
      // загружаем версию из списка параметров, при этом отсекаем концевые
      // пробелы и пробелы в середине строки.
      vVersionTFF := StringReplace( LowerCase( TRIM( aParamTFF)), ' ', '');
      // если версия не указана - берем по умолчанию
      if (Length( vVersionTFF) <= 0)
        then vVersionTFF := cnCurrentVersionTFF;

      // определяем текущий каталог файлов схем
      xVersionDir := '';
	  // Exchange, #3270  - добавлена доп.схема 22.0
      if IsVersionSupport( cnVersionTFF_21)
        then xVersionDir := '21'
        else
	  // Exchange, #3163  - добавлена доп.схема 21.0
      if IsVersionSupport( cnVersionTFF_20_3)
        then xVersionDir := '20_3'
        else
	  // АЦК-Ф, #22954  - добавлена доп.схема 20.3
      if IsVersionSupport( cnVersionTFF_20_2)
        then xVersionDir := '20_2'
        else
	  // exchange #3147 - добавлена доп.схема 20.2
      if IsVersionSupport( cnVersionTFF_20)
        then xVersionDir := '20'
        else
      // exchange #3078 - добавлена доп.схема 20
      if IsVersionSupport( cnVersionTFF_19)
        then xVersionDir := '19'
        else
      // exchange #3040 - добавлена доп.схема 19
      if IsVersionSupport( cnVersionTFF_18)
        then xVersionDir := '18'
        else
      // exchange #2996 - добавлена доп.схема 18
      if IsVersionSupport( cnVersionTFF_17)
        then xVersionDir := '17'
        else
{ не удалять символ точки с запятой ---->};
      vVersionTFF_Dir := ValidateDirectoryString( aDirIni)
                          + 'Schems' + xVersionDir + '\';

      // загружаем список файлов схемы только из каталога схемы (без вложенных
      // подкаталогов), с преобразованием имен файлов в нижний регистр.
      // в списке файлы определяются без каталогов - только имена файлов.
      // на случай повторной инициализации чистим список файлов.
//      vVersionTFF_Files.Clear;
//      ScanFiles( vVersionTFF_Dir, '*.xml', vVersionTFF_Files, FALSE, TRUE, TRUE);
      except
        ExceptionLog( 'Сбой Un_VersionSupport.ParseParameterVersionSupport');
    end;

end;


{~GetFileNameSchemaTFF
  возвращает полное имя файла ТФФ для текущей схемы по шаблону имени файла.
  расширение файла .xml будет добавлено к строке aMaska в этой функции.
  регистр указания строки не важен. в функции всегда выполняется перевод строки
  к нижнему регистру.

  в принципе, достаточно указать, например, такую строку: fp_txfp*
  в результате будет возвращен полный путь к файлу "путь\fp_txfp100701.xml".

  в этом случае не нужно в скриптах проверять текущую схему и делать
  сравнение:
    если схема ... то загружать файл ...
    иначе
    если схема ... то загружать файл ...
    ...

  теперь достаточно в подпрограмму инициализации подставить загрузку
  схемы из конфига и в код получения имени файла схемы подставить эту функцию
  с указанием маски файла, например так:
    LoadSchema( GetFileNameSchemaTFF( 'vt_txvt*'));

  тогда код скриптов станет универсальным и его не нужно будет менять
  при переходе от схемы к схеме.
  достаточно поменять в этом модуле константы описания каталога схем для
  возможных выборов ТФФ в конфиге.

  возбуждается исключение, если в каталоге текущей схемы, по маске будет
  найдено несколько файлов или ни одного.}
function GetFileNameSchemaTFF( aMaska: string): string;
var
//  i: integer;
  s: string;
  xFileSearch: TFileSearch;
begin

    try
      s := LOWERCASE( aMaska) + '.xml';

      xFileSearch := TFileSearch.Create;
      try
        if xFileSearch.FindFirst(vVersionTFF_Dir + s, faAnyFile) = 0 then
          try
            Result := vVersionTFF_Dir + xFileSearch.Name;
            if (xFileSearch.FindNext <> 0) then begin

              EXIT;
            end;
            finally
              xFileSearch.FindClose;
          end;
        finally
          xFileSearch.Free;
      end;

(*
      for i := 0 to vVersionTFF_Files.Count - 1 do
        if MatchesMask( vVersionTFF_Files.Strings[i], s) then begin
          Result := vVersionTFF_Dir + vVersionTFF_Files.Strings[i];

          EXIT;
        end;
        *)
      raise( FORMAT( 'Указанный файл схемы (по ТФФ %s) по маске "%s" не найден (или найден неоднозначно) в каталоге "%s".',
                      [ vVersionTFF,
                        aMaska,
                        vVersionTFF_Dir]));

      except
        ExceptionLog( 'Сбой Un_VersionSupport.GetFileNameSchemaTFF');
    end;

end;


{~InitializeUnVersionSupport
  инициализация модуля Un_VersionSupport}
procedure InitializeUnVersionSupport;
begin

    try
      vVersionTFF := cnCurrentVersionTFF;
      vFormVer    := '';
      (*
      // при первом запуске скрипта переменная =NIL.
      // при работе скрипта и вторичной инициализации модуля, переменную
      // нельзя создавать, иначе будут утечки памяти.
      if (vVersionTFF_Files = NIL)
        then vVersionTFF_Files:= TStringList.Create
        else vVersionTFF_Files.Clear;
        *)
      except
        ExceptionLog( 'Сбой Un_VersionSupport.InitializeUnVersionSupport');
    end;

end;

{~FinalizeUnVersionSupport
  завершение работы модуля Un_VersionSupport.}
procedure FinalizeUnVersionSupport;//( aParam: Variant);
begin

    try
      (*
        // при первом завершении скрипта переменная должна быть разрушена
        // и перезаписана на NIL.
        // при работе скрипта и вторичном вызове подпрограммы завершения модуля,
        // переменную нельзя разрушать, иначе будут сбои.
        if (vVersionTFF_Files <> NIL) then begin
          vVersionTFF_Files.Free;
          vVersionTFF_Files := NIL;
        end;
        *)
      //FinalizeDebug;
      //showmessage('finalize');
      except
        ExceptionLog( 'Сбой Un_VersionSupport.FinalizeUnVersionSupport');
    end;

end; {End Finalize}


begin
  {!не удалять этот комментарий!
  InitializeProgressControl;  // инициализация модуля контролов отображения прогресса
  InitializeDebug;        // инициализация модуля отладки
  InitializeLog( 'Un_VersionSupport', 'FinalizeUnVersionSupport');  // инициализация модуля журналов
  {!не удалять этот комментарий!}
  InitializeUnVersionSupport;
  {!не удалять этот комментарий!
  CompareVersionAZK_Finance( 'Проверка', '2.26');
  CompareVersionAZK_Finance( 'Проверка', '2.27');
  CompareVersionAZK_Finance( 'Проверка', '2.28');
  CompareVersionAZK_Finance( 'Проверка', '2.29');
  {
  showmessage( IsVersionSupport( '1,2,3,8,9'));
  showmessage( IsVersionSupport( '1,2,9,8'));
  showmessage( IsVersionSupport( '9,8,2'));
  showmessage( IsVersionSupport( '8,2'));
  showmessage( IsVersionSupport( '9'));
  showmessage( IsVersionSupport( ''));
  {!не удалять этот комментарий!}
end.
