/// <summary>Обработка транзакции. Если обработка транзакции завершилась успешно - возвращается true</summary>
        bool ProcessTransactionItem(OracleConnection Connection, RecalculationTransactionItem Item, SharedDataInfo SharedData)
        {
            // блокируем транзакцию
              OraQuery.Execute(Connection, "select 1 from Transactions where ID = :ID for update nowait", new string[] { "ID" }, new object[] { Item.ID });

              // создаём скрипт и транзакцию, загружаем её, обрабатываем, записываем статистику
              var CurrentScript = new ScriptInfo(TransactionalMechanismTypes.UseConnection, SharedData, new FileLogger(""), null);
              if (!Item.IsCorrectSignature)
            CurrentScript.ErrorMessage.Add("Некорректная подпись безопасности транзакции {0}. Пересчёт остановлен", Item.ID);
              if (CurrentScript.ErrorMessage.IsEmpty)
              {
            try
            {
              CurrentScript.Connection = Connection;
              TransactionInfo CurrentTransaction = CurrentScript.Transaction, OriginalTransaction, Difference;
              // заполняем поля, необходимые для формирования транзакции
              CurrentTransaction.PurchaseID = Item.ID;
              CurrentTransaction.TerminalDateTime = Item.TerminalDateTime;
              CurrentTransaction.Terminal.ID = Item.TerminalID;
              CurrentTransaction.Card.Number = Item.CardNumber;
              CurrentTransaction.Card.Contract.ID = Item.ContractID;
              CurrentTransaction.AboutDevice.SerialNumber = "0";
              CurrentTransaction.Flags = Item.Flags;
              CurrentTransaction.BehaviorFlags |= TransactionBehaviorFlags.AvoidChecks; // устанавливаем флаг игнорирования правил и проверок
              // у этого скрипта должна быть своя инициализация (без заполнения ID транзакции и проверки состояния терминала и карты
              if (CurrentScript.InitializeTerminal(false) && CurrentScript.InitializeCardFirst()) // инициализируем теримнал без проверки состояния и карту
              {
            CurrentScript.SelectCardPlugin(Item.CardScriptID);
            CurrentScript.Config = new ScriptConfiguration(CurrentScript);
            CurrentScript.Config.Load(Item.ParamSetPeriodID); // используем конфигурацию скрипта, действующую на момент транзакции
            CurrentScript.InitializeCardLast(false); // завершаем инициализацию карты без вызова скриптовой инциализации, т.к. карта может работать со счётчиками, которые ещё не подготовлены
            CurrentScript.Card.StatusID = Item.CardStatusID; // изменяем статус карты на тот, который был в момент транзакции
            // Изменяем дату последнего обслуживания карты на ту, когда была транзакция. LastServiceDate влияет на загрузку предыдущих значений счётчиков.
            CurrentScript.Card.LastServiceDate = Item.CardLastServiceDateTime;

            // загружаем данные по транзакции (восстанавливаем состояние окружения на момент ДО транзакции
            CurrentTransaction.Load(Item.ID, true);
            CurrentTransaction.Card.Contract.OwnerAccounts.LoadFromTransaction(Item.ID, false);
            CurrentTransaction.Card.Contract.Accounts.LoadFromTransaction(Item.ID, false);
            CurrentTransaction.Card.Counters.LoadFromTransaction(Item.ID, false); // 02/04/15 ещё можно подумать - а если в транзакции не было счётчика, а сейчас на карте есть? Может быть стОит очищать счётчики перед загрузкой из транзакции?
            CurrentTransaction.Card.CountersSP.LoadFromTransaction(Item.ID, false);
            CurrentTransaction.Contract.Counters.LoadFromTransaction(Item.ID, false);

            CurrentScript.CurrentCardScript.InitializeCard(); // вызываем скриптовую функцию инициализации карты

            var CurrentContract = CurrentScript.Card.Contract;
            if (Request.Type == RecalculationRequestTypes.Compensation)
            {
              if (Request.LastContract == null || Request.LastContract.ID != CurrentContract.ID)
              {
                Request.LastContract = CurrentContract; // устанавливаем текущий договор пересчёта
                CurrentContract.DataAccumulators = new DataAccumulatorsInfo(CurrentContract, Request); // Если пересчёт компенсационный, то разрешаем долгие процедуры
              }
              else if (Request.LastContract != null && Request.LastContract.ID == CurrentContract.ID)
              {
                CurrentContract.DataAccumulators = Request.LastContract.DataAccumulators;
                CurrentContract.DataAccumulators.Contract = CurrentContract; // это нужно для того, чтобы аккумулятор брал корректные значения текущей транзакции
              }
            }

            if (CurrentScript.ErrorMessage.IsEmpty)
            {
              CurrentTransaction.RSPackage.Calculate(); // пересчитываем чек транзакции до расчёта
              OriginalTransaction = CurrentTransaction.Clone(); // сохраняем эталон пакета ККМ перед расчётами
              OriginalTransaction.Load(Item.ID, false); // загружаем данные чека на момент ПОСЛЕ транзакции
              OriginalTransaction.RSPackage.Calculate(); // пересчитываем чек после загрузки

              if (CurrentScript.ProvideCalculateReceipt()) // вызываем функции пересчёта чека и покупки
                CurrentScript.ProvidePurchase(false, false);

              /*// Заменено 20/08/2014, т.к. только скриптовые функции не сделают всей работы
                // вызываем скриптовые функции расчёта и покупки
              if (!Script.Fiscal.Flags.HasFlag(FiscalReceiptFlags.WithoutRecalculation)) // если нет флага "БЕЗ ПЕРЕСЧЁТА"
                Script.CurrentCardScript.ChangeReceipt(); // делаем пересчёт чека
              Current.RSPackage.Calculate(); // пересчитываем чек после расчёта
              Script.CurrentCardScript.Purchase(); // проводим покупку */
              // в Current сохраняем неизменной пересчитанную транзакцию

              if (CurrentScript.ErrorMessage.IsEmpty && Request.Type == RecalculationRequestTypes.Compensation) // при компенсационном пересчёте добавляем текущую транзакцию в накопление
              {
                // выбираем все градации с периодами "текущий пересчёт", используемые договором
                var ContractGraduations = CurrentContract.GetGraduationsForPeriod(GraduationPeriods.CurrentRecalculationPeriod);

                foreach (var Graduation in ContractGraduations)
                {
                  // проверяем, можно ли текущую транзакцию добавлять в накопитель градации
                  var RestrictionData = OraQuery.Execute(Connection, "select opsOnlineRestrictions.CheckGraduation(:GraduationID, :TerminalID, null, null, null, null, :ContractID, null, null, null) from dual",
                    new string[] { "GraduationID", "TerminalID", "ContractID" }, new object[] { Graduation.ID, CurrentTransaction.Terminal.ID, CurrentContract.ID });

                  if (Cvt.ToInt32(RestrictionData[0, 0]) != 0)
                  {
                    var Accumulator = CurrentContract.DataAccumulators.GetAccumulator(Graduation.ID); // накопитель текущего пересчёта (для плавного изменения скидки)
                    if (Accumulator != null)
                    {
                      var TransactionAccumulator = new DataAccumulatorInfo(CurrentContract.DataAccumulators, Graduation); // создаём транзакционный накопитель для текушей градации
                      TransactionAccumulator.LoadFromCurrentTransaction(); // загружаем данные текущей транзакции в накопитель
                      Accumulator.Add(TransactionAccumulator); // добавляем к накопителю текущего пересчёта, накопитель текущей транзакции
                    }
                  }
                }
              }

              Difference = CurrentTransaction.Clone();
              if (CurrentScript.ErrorMessage.IsEmpty)
              {
                // рассчитываем разницу
                Difference.RSPackage = RSPackageInfo.Substract(Difference, CurrentTransaction.RSPackage, OriginalTransaction.RSPackage); // вычитаем пересчитанный пакет транзакции из оригинального
                Difference.Card.Counters.CompareWithTransaction(Item.ID);          // в difference лежат изменения относительно текущей транзакции
                Difference.Card.CountersSP.CompareWithTransaction(Item.ID, Difference.Terminal.ServicePoint.ID);        // в difference нас интересуют ТОЛЬКО ИЗМЕНЕНИЯ счётчиков и счетов. Всё остальныое - игнорируем
                Difference.Contract.Counters.CompareWithTransaction(Item.ID);          // в difference лежат изменения относительно текущей транзакции
                Difference.Contract.Accounts.CompareWithTransaction(Item.ID);
                Difference.Contract.OwnerAccounts.CompareWithTransaction(Item.ID);
              }

              // определяем есть ли что сохранять...
              if (CurrentScript.ErrorMessage.IsEmpty && !Difference.IsZero)
              {
                // known issue: if only messages was changed they will not be updated. Data change is strongly required (is necessarily)

                // Сохраняем изменения транзакции
                Log.Message("Сохранение изменённой транзакции " + CurrentTransaction.PurchaseID);

                if (CurrentTransaction.Update()) // обновляем пересчитанную транзакцию
                {
                  // перед сохранением разницы устанавливаем причину (не транзакция, а пересчёт транзакции)
                  Difference.Contract.Counters.Reason = ChangeReasons.Recalculation;
                  Difference.Contract.Accounts.Reason = ChangeReasons.Recalculation;
                  Difference.Contract.OwnerAccounts.Reason = ChangeReasons.Recalculation;
                  Difference.Card.Counters.Reason = ChangeReasons.Recalculation;
                  Difference.Card.CountersSP.Reason = ChangeReasons.Recalculation;
                  Difference.Card.Save(true); // изменяем значения счётчиков и счетов договора на величину разницы между текущей транзакцией и оригинальной
                  Item.IsChanged = true;
                }
              }
            }
              }
            }
            catch (Exception e)
            {
              CurrentScript.ErrorMessage.Add("Неизвестная ошибка: {0}", e.Message);
              Log.Message("Recalculator.ProcessTransactionItem(): {0}\n{1}", e.Message, e.StackTrace);
            }
            finally
            {
              if (CurrentScript.ErrorMessage.IsEmpty)
              {
            CurrentScript.SetReadyForCommit();
            CurrentScript.ProvideCommit();
              }
              else
              {
            Log.Message("Ошибка при обработке транзакции {0}\n{1}", Item.ID, CurrentScript.ErrorMessage.ToString());
            Item.ErrorMessage = CurrentScript.ErrorMessage.ToString();
            CurrentScript.Rollback();
              }

              CurrentScript.ReleaseResources();
            }
              }

              return CurrentScript.ErrorMessage.IsEmpty;
        }
        /// <summary>Заполняет Request.Identificators идентификаторами транзакций для пересчёта</summary>
        void GetTransactions(OracleConnection Connection, SharedDataInfo SharedData)
        {
            // выбираем все неотменённые открытые транзакции дебета, обработанные текущим ПЦ и удовлетворяющие условиям
              string SQL = @"select t.ID, t.TerminalID, t.CardNumber, t.ParamSetPeriodID, t.TerminalDateTime, t.ContractID, t.CardStatus, t.Signature, t.FlagsInitial, t.CardLastServiceDate, t.CardScriptID
             from Transactions t, Contracts ct
            where t.ContractID = ct.ID
              and ct.OwnerID = :ContractOwnerID
              and t.AccountingPeriodID is null
              and t.TransactionType = 0
              and not exists (select 1 from TransactionCancellation z where t.ID = z.TransactionID and t.TerminalDateTime = z.TerminalDateTime)
              and t.TerminalDateTime between :DateFrom and :DateTo
              and t.CardScriptID is not null
              and t.HandlerPCID = :CurrentPCID "; // только транзакции текущего ПЦ
              var Params = new List<string>(new string[] { "ContractOwnerID", "DateFrom", "DateTo", "CurrentPCID" });
              var Values = new List<object>(new object[] { Request.ContractOwnerID, Request.DateFrom, Request.DateTo, SharedData.ProcessingCenters.Current.ID });
              // доп.фильтры
              if (Request.HasTerminalsFilter)
              {
            SQL += " and t.TerminalID in (select TerminalID from RecalculationTerminals where RecalcID = :RecalcID_Terminals) ";
            Params.Add("RecalcID_Terminals");
            Values.Add(Request.ID);
              }
              if (Request.HasContractsFilter)
              {
            SQL += " and t.ContractID in (select ContractID from RecalculationContracts where RecalcID = :RecalcID_Contracts) ";
            Params.Add("RecalcID_Contracts");
            Values.Add(Request.ID);
              }
              if (Request.HasCardsFilter)
              {
            SQL += " and t.CardNumber in (select CardNumber from RecalculationCards where RecalcID = :RecalcID_Cards) ";
            Params.Add("RecalcID_Cards");
            Values.Add(Request.ID);
              }
              if (Request.HasCardTypesFilter)
              {
            SQL += " and t.CardNumber in (select CardNumber from Cards where CardType in (select CardType from RecalculationCardTypes where RecalcID = :RecalcID_CardTypes)) ";
            Params.Add("RecalcID_CardTypes");
            Values.Add(Request.ID);
              }
              if (Request.HasTransactionsFilter)
              {
            SQL += " and t.ID in (select TransactionID from RecalculationTransactions where RecalcID = :RecalcID_Transactions) ";
            Params.Add("RecalcID_Transactions");
            Values.Add(Request.ID);
              }
              SQL += " order by t.ContractID, t.TerminalDateTime, t.ID ";

              var Data = OraQuery.Execute(Connection, SQL, Params.ToArray(), Values.ToArray());
              foreach (DataRow row in Data.Rows)
              {
            RecalculationTransactionItem Item = new RecalculationTransactionItem();
            Item.ID = Cvt.ToString(row["ID"]);
            Item.TerminalID = Cvt.ToUInt32(row["TerminalID"]);
            Item.CardNumber = Cvt.ToString(row["CardNumber"]);
            Item.CardStatusID = Cvt.ToUInt32(row["CardStatus"]);
            Item.ContractID = Cvt.ToUInt64(row["ContractID"]);
            Item.ParamSetPeriodID = Cvt.ToUInt32(row["ParamSetPeriodID"]);
            Item.TerminalDateTime = Cvt.ToDateTime(row["TerminalDateTime"]);
            Item.Signature = Cvt.ToByteArray(row["Signature"]);
            Item.Flags = (TransactionFlags)Cvt.ToUInt16(row["FlagsInitial"]);
            Item.CardLastServiceDateTime = Cvt.ToDateTime(row["CardLastServiceDate"]);
            Item.CardScriptID = Cvt.ToUInt32(row["CardScriptID"]);
            Request.Transactions.Add(Item);
              }
        }
        public SharedDataInfo Data = null; // Данные указанного поколения

        #endregion Fields

        #region Constructors

        public SharedDataGenerationItem(Int32 Generation, SharedDataInfo Data)
        {
            Data.Generation = Generation;
              this.Data = Data;
        }
 /// <summary>С помощью данного метода сессия уведомляет Loader, что указанные ОРД ей больше не нужны (во избежание накапливания устаревших поколений в ОЗУ).</summary>
 public void Release(SharedDataInfo SharedData)
 {
     //var PerfLog = new PerfomanceLogger(Log, "Возвращение ОРД");
       switch (Mechanism)
       {
     case SharedDataMechanismTypes.Clone: break;
     case SharedDataMechanismTypes.Generation:
       lock (AccessLock)
     Generations[SharedData.Generation].Counter--;
       break;
     default:
       throw new Exception("Unsupported SharedDataMechanismTypes: " + Mechanism);
       }
       //PerfLog.Stop();
 }
        // Основной цикл while (!NeedToUpdateSharedData) Sleep(1000 ms)
        // При этом через заданный таймаут - принудительное обновление SharedData
        /*
        /// <summary>Проверка нотификатора на живучесть</summary>
        private void TestNotifier()
        {
          try
          {
        var Data = OraQuery.Execute(NotifierConnection, "select 1 from dual");
          }
          catch (Exception emain)
          {
        Log.Message("Обнаружена ошибка отслеживаний изменений в БД: {0}", emain.Message);
        // если была ошибка - перезапускаем уведомителя
        RestartNotifier();
          }
        }
         */
        public bool Load()
        {
            bool Result = false;
              Log.Message("Загрузка общих разделяемых данных...");
              try
              {
            using (OracleConnection Connection = new OracleConnection(ConnectionString))
            {
              try
              {
            Connection.Open();
            SharedDataInfo Candidate = new SharedDataInfo(Connection, Log, UpdatePath, UpdateBlockSize, CurrentSharedData == null ? null : CurrentSharedData.CardScripts);
            // синхронно обновляем ссылки
            Log.Message("Замена поколения #{0}...", CurrentGeneration);
            lock (AccessLock)
            {
              CurrentGeneration++;
              CurrentSharedData = Candidate;

              if (Mechanism == SharedDataMechanismTypes.Generation)
              {
                foreach (SharedDataGenerationItem Item in Generations.Values.ToArray()) // проверяем словарь на наличие неиспользуемых записей и удалить их
                  if (Item.Counter <= 0)
                  {
                    Generations.Remove(Item.Data.Generation);
                    //Log.Message("Поколение #{0} удалено из списка доступных", Item.Data.Generation);
                  }
                Generations.Add(CurrentGeneration, new SharedDataGenerationItem(CurrentGeneration, CurrentSharedData));
              }
            }
            //Log.Message("...на поколение #{0} завершена", CurrentGeneration);
            Result = true;
              }
              catch (Exception e)
              {
            Log.Message("Загрузка кандидата: " + e.Message + "\n" + e.StackTrace);
              }
            }
              }
              catch (Exception e)
              {
            Log.Message("Общая ошибка загрузки: " + e.Message + "\n" + e.StackTrace);
              }
              Log.Message("...завершена " + (Result ? "успешно. " + string.Format("Поколение #{0}", CurrentGeneration) : "с ошибками"));
              //Log.Message("Использовано памяти: {0:n0}", GC.GetTotalMemory(false));
              return Result;
        }
        /*private void UpdateTra(OracleConnection Connection)
        {
          int Count = 0;
          var Data = OraQuery.Execute(Connection, "select ID, Signature from Transactions for update nowait");
          foreach (DataRow row in Data.Rows)
          {
        string ID = Cvt.ToString(row["ID"]);
        byte[] Signature = Cvt.ToByteArray(row["Signature"]);

        if (TransactionInfo.ExtractSignature(Signature) != ID)
        {
          Signature = TransactionInfo.GetSignature(ID);
          OraQuery.ExecuteNonQuery(Connection, "update Transactions set Signature = :Signature where ID = :ID", new string[] { "ID", "Signature" }, new object[] { ID, Signature });
          Count++;
        }
          }
          Connection.Commit();
          System.Windows.Forms.MessageBox.Show("Обновлено " + Count);
        }*/
        /// <summary>Возвращает клон общих разделяемых данных</summary>
        public SharedDataInfo Clone()
        {
            SharedDataInfo Result = new SharedDataInfo();
              // поля не клонируем, т.к. они не меняются после загрузок
              Result.Calc = Calc;
              Result.UpdatePath = UpdatePath;
              Result.UpdateBlockSize = UpdateBlockSize;
              Result.CardScripts = CardScripts;

              // Порядок клонирования важен. Не менять!
              Result.CardScripts = CardScripts.Clone(Result);
              Result.PCParameters = PCParameters.Clone();
              Result.Owners = Owners.Clone(Result);
              Result.ProcessingCenters = ProcessingCenters.Clone(Result);
              Result.Devices = Devices.Clone();
              Result.DevicesGroups = DevicesGroups.Clone(Result);
              Result.Networks = Networks.Clone(Result);
              Result.Currencies = Currencies.Clone();
              Result.TimeOffsetPeriods = TimeOffsetPeriods.Clone();
              Result.Regions = Regions.Clone(Result);
              Result.RetailSystems = RetailSystems.Clone(Result);
              Result.ServicePoints = ServicePoints.Clone(Result);
              Result.Terminals = Terminals.Clone(Result);
              Result.ProductGoods = ProductGoods.Clone(Result);
              Result.ProductGroups = ProductGroups.Clone();
              Result.MeasureUnits = MeasureUnits.Clone();
              Result.Products = Products.Clone(Result);
              Result.Purses = Purses.Clone(Result);
              Result.Statuses = Statuses.Clone();
              Result.Parameters = Parameters.Clone();
              Result.ParameterSets = ParameterSets.Clone(Result);
              Result.Counters = Counters.Clone(Result);
              Result.Graduations = Graduations.Clone(Result);
              Result.GraduationGroups = GraduationGroups.Clone(Result);
              Result.Tariffs = Tariffs.Clone(Result);
              Result.CardRanges = CardRanges.Clone(Result);
              Result.DataElements = DataElements.Clone(Result);
              Result.EventActionTypes = EventActionTypes.Clone();
              Result.Events = Events.Clone(Result);
              Result.EmailProviders = EmailProviders.Clone(Result);
              Result.SMSProviders = SMSProviders.Clone(Result);
              Result.DBVersion = DBVersion.Clone(Result);

              return Result;
        }