/// <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; }