/// <summary> /// Update a consumer. /// </summary> /// <param name="entity">The ConsumerTrust's Entity row.</param> /// <returns>The ConsumerId of the Consumer row.</returns> private Guid UpdateConsumer(EntityRow entity) { DataModel dataModel = new DataModel(); DataModelTransaction dataModelTransaction = DataModelTransaction.Current; DateTime modified = DateTime.UtcNow; CountryRow country; Guid countryId; Guid? provinceId = null; EntityRow dollars; Guid dollarsId; ConsumerRow consumer = null; ConsumerTrustRow consumerTrust = null; SecurityRow security = null; WorkingOrderRow workingOrder = null; MatchRow[] matches; Guid consumerId; Guid consumerTrustId; Guid entityId; Guid securityId; Guid workingOrderId; Int64 consumerVersion; Int64 consumerTrustVersion; Int64 entityVersion; Int64 securityVersion; Int64 workingOrderVersion; Boolean updateConsumer = false; Boolean updateConsumerTrust = false; Boolean updateEntity = false; Boolean updateSecurity = false; country = TradingSupport.FindCountryByKey( this.Record.ConfigurationId, "FK_Country_Security", new object[] { this.Record.CountryCode }); countryId = country.CountryId; country.ReleaseReaderLock(dataModelTransaction.TransactionId); if (this.Record.ProvinceCode != null) { ProvinceRow province = TradingSupport.FindProvinceByKey( this.Record.ConfigurationId, "FK_Province_Consumer", new object[] { this.Record.ProvinceCode }); provinceId = province.ProvinceId; province.ReleaseReaderLock(dataModelTransaction.TransactionId); } dollars = TradingSupport.FindEntityByKey( this.Record.ConfigurationId, "FK_Security_WorkingOrder_SettlementId", new object[] { this.Record.Currency }); dollarsId = dollars.EntityId; dollars.ReleaseReaderLock(dataModelTransaction.TransactionId); try { entity.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); entityId = entity.EntityId; entityVersion = entity.RowVersion; if (TradingSupport.IsColumnOld(entity, "Name", this.Record.OriginalAccountNumber)) { updateEntity = true; } } finally { entity.ReleaseLock(dataModelTransaction.TransactionId); } try { security = DataModel.Security.SecurityKey.Find(entityId); security.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); securityId = entityId; securityVersion = security.RowVersion; workingOrder = security.GetWorkingOrderRowsByFK_Security_WorkingOrder_SecurityId()[0]; if (TradingSupport.IsColumnOld(security, "CountryId", countryId)) { updateSecurity = true; } } finally { security.ReleaseLock(dataModelTransaction.TransactionId); } // Control the working order: workingOrder.AcquireWriterLock(dataModelTransaction); try { consumerTrust = DataModel.ConsumerTrust.ConsumerTrustKey.Find(entityId); consumerTrust.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); consumerTrustId = consumerTrust.ConsumerTrustId; consumerTrustVersion = consumerTrust.RowVersion; consumer = DataModel.Consumer.ConsumerKey.Find(consumerTrust.ConsumerId); if (TradingSupport.IsColumnOld(consumerTrust, "SavingsAccount", this.Record.SavingsAccount) || TradingSupport.IsColumnOld(consumerTrust, "SavingsBalance", this.Record.SavingsBalance) || TradingSupport.IsColumnOld(consumerTrust, "Tag", this.Record.Tag)) { updateConsumerTrust = true; } } finally { consumerTrust.ReleaseLock(dataModelTransaction.TransactionId); } try { consumer.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); consumerId = consumer.ConsumerId; consumerVersion = consumer.RowVersion; if (TradingSupport.IsColumnOld(consumer, "Address1", this.Record.Address1) || TradingSupport.IsColumnOld(consumer, "Address2", this.Record.Address2) || TradingSupport.IsColumnOld(consumer, "BankAccountNumber", this.Record.BankAccountNumber) || TradingSupport.IsColumnOld(consumer, "BankRoutingNumber", this.Record.BankRoutingNumber) || TradingSupport.IsColumnOld(consumer, "City", this.Record.City) || TradingSupport.IsColumnOld(consumer, "DateOfBirth", this.Record.DateOfBirth) || TradingSupport.IsColumnOld(consumer, "FirstName", this.Record.FirstName) || TradingSupport.IsColumnOld(consumer, "IsEmployed", this.Record.IsEmployed) || TradingSupport.IsColumnOld(consumer, "LastName", this.Record.LastName) || TradingSupport.IsColumnOld(consumer, "PostalCode", this.Record.PostalCode) || TradingSupport.IsColumnOld(consumer, "MiddleName", this.Record.MiddleName) || TradingSupport.IsColumnOld(consumer, "PhoneNumber", this.Record.PhoneNumber) || TradingSupport.IsColumnOld(consumer, "ProvinceId", provinceId) || TradingSupport.IsColumnOld(consumer, "SocialSecurityNumber", this.Record.SocialSecurityNumber) || TradingSupport.IsColumnOld(consumer, "Suffix", this.Record.Suffix)) { updateConsumer = true; } } finally { consumer.ReleaseLock(dataModelTransaction.TransactionId); } try { //workingOrder.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); workingOrderId = workingOrder.WorkingOrderId; workingOrderVersion = workingOrder.RowVersion; matches = workingOrder.GetMatchRows(); } finally { //workingOrder.ReleaseLock(dataModelTransaction.TransactionId); } foreach (MatchRow match in matches) { if (WorkingOrderPersistence.IsSettled(dataModelTransaction, match)) { throw new FaultException <SecurityFault>( new SecurityFault("Cannot update account that is settled") { FaultCode = ErrorCode.RecordExists }, "Cannot update account that is settled"); } } // We need write access to the containing blotter in order to add a record to it. if (!TradingSupport.HasAccess(dataModelTransaction, PersistenceHelper.GetBlotterForConsumer(dataModelTransaction, consumerId), AccessRight.Write)) { throw new SecurityException("Current user does not have write access to the original blotter"); } if (updateConsumer) { dataModel.UpdateConsumer( this.Record.Address1 != null ? (object)this.Record.Address1 : DBNull.Value, this.Record.Address2 != null ? (object)this.Record.Address2 : DBNull.Value, this.Record.BankAccountNumber != null ? (object)this.Record.BankAccountNumber : DBNull.Value, this.Record.BankRoutingNumber != null ? (object)this.Record.BankRoutingNumber : DBNull.Value, this.Record.City != null ? (object)this.Record.City : DBNull.Value, consumerId, new object[] { consumerId }, this.Record.DateOfBirth != null ? (object)this.Record.DateOfBirth.Value : DBNull.Value, null, null, this.Record.FirstName != null ? (object)this.Record.FirstName : DBNull.Value, this.Record.IsEmployed != null ? (object)this.Record.IsEmployed.Value : DBNull.Value, this.Record.LastName != null ? (object)this.Record.LastName : DBNull.Value, this.Record.MiddleName != null ? (object)this.Record.MiddleName : DBNull.Value, this.Record.PhoneNumber != null ? (object)StringUtilities.CleanUpAlphaNumericString(this.Record.PhoneNumber) : DBNull.Value, this.Record.PostalCode != null ? (object)this.Record.PostalCode : DBNull.Value, provinceId, consumerVersion, this.Record.Salutation, StringUtilities.CleanUpAlphaNumericString(this.Record.SocialSecurityNumber), this.Record.Suffix != null ? (object)this.Record.Suffix : DBNull.Value); } if (updateConsumerTrust) { dataModel.UpdateConsumerTrust( null, consumerTrustId, new object[] { consumerTrustId }, null, null, consumerTrustVersion, this.Record.SavingsAccount, this.Record.SavingsBalance, this.Record.Tag != null ? (object)this.Record.Tag : DBNull.Value, null, null); } if (updateEntity) { dataModel.UpdateEntity( null, null, entityId, new object[] { entityId }, null, null, null, null, null, null, null, null, null, null, null, modified, this.Record.SavingsEntityCode, entityVersion, null, null); } if (updateSecurity) { dataModel.UpdateSecurity( null, countryId, null, null, null, 1, 1, securityVersion, securityId, new object[] { securityId }, null, null, null); } dataModel.UpdateWorkingOrder( null, this.Record.Blotter, null, null, null, null, null, null, null, null, null, null, null, modified, TradingSupport.UserId, null, workingOrderVersion, null, null, dollarsId, null, null, StatusMap.FromCode(Status.New), null, null, null, null, null, null, modified, workingOrderId, new object[] { workingOrderId }); return(consumerId); }
/// <summary> /// Create a new consumer. /// </summary> /// <returns>The ConsumerId of the consumer.</returns> private Guid CreateConsumer() { DataModel dataModel = new DataModel(); DataModelTransaction dataModelTransaction = DataModelTransaction.Current; Guid userId = TradingSupport.UserId; Guid tenantId = PersistenceHelper.GetTenantForEntity(DataModelTransaction.Current, this.Record.Blotter); Guid entityId = Guid.NewGuid(); Guid consumerId = Guid.NewGuid(); Guid creditCardId = Guid.NewGuid(); Guid workingOrderId = Guid.NewGuid(); CountryRow country; Guid countryId; Guid? provinceId = null; EntityRow dollars; Guid dollarsId; TypeRow type; Guid typeId; ImageRow image; Guid imageId; DateTime currentUTCTime = DateTime.UtcNow; country = TradingSupport.FindCountryByKey( this.Record.ConfigurationId, "FK_Country_Security", new object[] { this.Record.CountryCode }); countryId = country.CountryId; country.ReleaseReaderLock(dataModelTransaction.TransactionId); if (this.Record.ProvinceCode != null) { ProvinceRow province = TradingSupport.FindProvinceByKey( this.Record.ConfigurationId, "FK_Province_Consumer", new object[] { this.Record.ProvinceCode }); provinceId = province.ProvinceId; province.ReleaseReaderLock(dataModelTransaction.TransactionId); } dollars = TradingSupport.FindEntityByKey( this.Record.ConfigurationId, "FK_Security_WorkingOrder_SettlementId", new object[] { this.Record.Currency }); dollarsId = dollars.EntityId; dollars.ReleaseReaderLock(dataModelTransaction.TransactionId); image = TradingSupport.FindImageByKey( this.Record.ConfigurationId, "FK_Image_Entity", new object[] { "OBJECT" }); imageId = image.ImageId; image.ReleaseReaderLock(dataModelTransaction.TransactionId); type = TradingSupport.FindTypeByKey( this.Record.ConfigurationId, "FK_Type_Entity", new object[] { "CONSUMER TRUST" }); typeId = type.TypeId; type.ReleaseReaderLock(dataModelTransaction.TransactionId); dataModel.CreateEntity( currentUTCTime, null, entityId, null, null, null, null, null, null, null, this.Record.CustomerCode, imageId, false, false, currentUTCTime, this.Record.SavingsEntityCode, tenantId, typeId); dataModel.CreateSecurity( null, countryId, null, null, null, 1, 1, entityId, this.Record.AccountCode, tenantId, VolumeCategoryMap.FromCode(VolumeCategory.Unknown)); dataModel.CreateConsumer( this.Record.Address1, this.Record.Address2, this.Record.BankAccountNumber, this.Record.BankRoutingNumber, this.Record.City, consumerId, this.Record.DateOfBirth != null ? (object)this.Record.DateOfBirth.Value : null, null, null, this.Record.FirstName, this.Record.IsEmployed != null ? (object)this.Record.IsEmployed.Value : null, this.Record.LastName, this.Record.MiddleName, StringUtilities.CleanUpAlphaNumericString(this.Record.PhoneNumber), this.Record.PostalCode, provinceId, this.Record.Salutation, StringUtilities.CleanUpAlphaNumericString(this.Record.SocialSecurityNumber), this.Record.Suffix); dataModel.CreateConsumerTrust( consumerId, entityId, null, null, this.Record.SavingsAccount, this.Record.SavingsBalance, this.Record.Tag, tenantId, this.Record.VendorCode); //If this not found, there will be an exception. Let the exception propagate up. dataModel.CreateWorkingOrder( null, this.Record.Blotter, currentUTCTime, userId, CrossingMap.FromCode(Crossing.AlwaysMatch), null, null, null, null, true, true, true, null, DateTime.UtcNow, TradingSupport.UserId, OrderTypeMap.FromCode(OrderType.Market), entityId, DateTime.UtcNow, dollarsId, SideMap.FromCode(Side.Sell), DateTime.UtcNow, StatusMap.FromCode(Status.New), null, null, null, null, TimeInForceMap.FromCode(TimeInForce.GoodTillCancel), DateTime.UtcNow, DateTime.UtcNow, workingOrderId); // Create the access control record for this new entity. dataModel.CreateAccessControl( Guid.NewGuid(), AccessRightMap.FromCode(AccessRight.FullControl), entityId, userId, tenantId); return(consumerId); }
/// <summary> /// Create a new Debt Holder Record /// </summary> /// <returns></returns> internal Guid Create() { DataModel dataModel = new DataModel(); DataModelTransaction dataModelTransaction = DataModelTransaction.Current; Guid userId = TradingSupport.UserId; Guid tenantId = PersistenceHelper.GetTenantForEntity(dataModelTransaction, this.Record.Blotter); Guid entityId = Guid.Empty; Guid consumerId; Guid creditCardId; Guid workingOrderId; CountryRow country; Guid countryId; Guid? provinceId = null; TypeRow type; Guid typeId; ImageRow image; Guid imageId; // These variables are used for auditing the changes to this record. DateTime createdTime = DateTime.UtcNow; Guid createdUserId = TradingSupport.UserId; DateTime modifiedTime = createdTime; Guid modifiedUserId = createdUserId; EntityRow dollars; Guid dollarsId; // We need write access to the containing blotter in order to add a record to it. if (!DataModelFilters.HasAccess(dataModelTransaction, userId, this.Record.Blotter, AccessRight.Write)) { throw new SecurityException("Current user does not have write access to the selected blotter"); } country = TradingSupport.FindCountryByKey( this.Record.ConfigurationId, "FK_Country_Security", new object[] { this.Record.CountryCode }); countryId = country.CountryId; country.ReleaseReaderLock(dataModelTransaction.TransactionId); if (this.Record.ProvinceCode != null) { ProvinceRow province = TradingSupport.FindProvinceByKey( this.Record.ConfigurationId, "FK_Province_Consumer", new object[] { this.Record.ProvinceCode }); provinceId = province.ProvinceId; province.ReleaseReaderLock(dataModelTransaction.TransactionId); } dollars = TradingSupport.FindEntityByKey( this.Record.ConfigurationId, "FK_Security_WorkingOrder_SettlementId", new object[] { this.Record.Currency }); dollarsId = dollars.EntityId; dollars.ReleaseReaderLock(dataModelTransaction.TransactionId); image = TradingSupport.FindImageByKey( this.Record.ConfigurationId, "FK_Image_Entity", new object[] { "OBJECT" }); imageId = image.ImageId; image.ReleaseReaderLock(dataModelTransaction.TransactionId); type = TradingSupport.FindTypeByKey( this.Record.ConfigurationId, "FK_Type_Entity", new object[] { "CONSUMER DEBT" }); typeId = type.TypeId; type.ReleaseReaderLock(dataModelTransaction.TransactionId); entityId = Guid.NewGuid(); consumerId = Guid.NewGuid(); creditCardId = Guid.NewGuid(); workingOrderId = Guid.NewGuid(); dataModel.CreateEntity( createdTime, null, entityId, null, null, null, null, null, null, null, this.Record.AccountCode, imageId, false, false, modifiedTime, this.Record.OriginalAccountNumber, tenantId, typeId); dataModel.CreateSecurity( null, countryId, null, null, null, 1, 1, entityId, this.Record.AccountCode, tenantId, VolumeCategoryMap.FromCode(VolumeCategory.Unknown)); dataModel.CreateConsumer( this.Record.Address1, this.Record.Address2, null, null, this.Record.City, consumerId, this.Record.DateOfBirth != null ? (object)this.Record.DateOfBirth.Value : null, null, null, this.Record.FirstName, null, this.Record.LastName, this.Record.MiddleName, StringUtilities.CleanUpAlphaNumericString(this.Record.PhoneNumber), this.Record.PostalCode, provinceId, null, StringUtilities.CleanUpAlphaNumericString(this.Record.SocialSecurityNumber), this.Record.Suffix); dataModel.CreateCreditCard( this.Record.AccountBalance, this.Record.AccountCode, consumerId, creditCardId, this.Record.DebtHolder, null, null, this.Record.AccountCode, StringUtilities.CleanUpAlphaNumericString(this.Record.OriginalAccountNumber), tenantId); dataModel.CreateConsumerDebt( this.Record.CollectionDate, entityId, consumerId, creditCardId, this.Record.DateOfDelinquency != null ? (object)this.Record.DateOfDelinquency.Value : null, null, null, this.Record.Representative, this.Record.Tag, tenantId, this.Record.VendorCode); dataModel.CreateWorkingOrder( null, this.Record.Blotter, createdTime, createdUserId, CrossingMap.FromCode(Crossing.AlwaysMatch), null, null, null, null, true, true, true, null, modifiedTime, modifiedUserId, OrderTypeMap.FromCode(OrderType.Market), entityId, createdTime, dollarsId, SideMap.FromCode(Side.Sell), createdTime, StatusMap.FromCode(Status.New), null, null, null, null, TimeInForceMap.FromCode(TimeInForce.GoodTillCancel), createdTime, createdTime, workingOrderId); // Create the access control record for this new entity. dataModel.CreateAccessControl( Guid.NewGuid(), AccessRightMap.FromCode(AccessRight.FullControl), entityId, userId, tenantId); return(entityId); }
/// <summary> /// Evaluates whether a given working order is eligible for a cross with another order. /// </summary> /// <param name="key">The key of the object to be handled.</param> /// <param name="parameters">A generic list of paraneters to the handler.</param> public static void MergeDocument(Object[] key, params Object[] parameters) { // Extract the strongly typed variables from the generic parameters. Guid consumerDebtSettlementId = (Guid)key[0]; // This structure will collect the information required for the merge operation. MergeInfo mergeInfo = new MergeInfo(); mergeInfo.ConsumerDebtSettlementId = consumerDebtSettlementId; // An instance of the data model is required for CRUD operations. DataModel dataModel = new DataModel(); // If two counterparties agree on a transaction then a settlement report is generated from the Word Template associated with the Consumer Debt // Entity. using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.RequiresNew, TimeSpan.FromHours(1))) { // This provides a context for any transactions. DataModelTransaction dataModelTransaction = DataModelTransaction.Current; // It is important to minimize the locking for these transactions since they will drag the system performance down and create deadlock // sitiations if too many are held for too long. BlotterRow blotterRow = null; ConsumerDebtSettlementRow consumerDebtSettlementRow = null; ConsumerDebtNegotiationRow consumerDebtNegotiationRow = null; MatchRow matchRow = null; CreditCardRow creditCardRow = null; WorkingOrderRow workingOrderRow = null; try { // The ConsumerDebtSettlement row is where the search for the Settlement Information begins. consumerDebtSettlementRow = DataModel.ConsumerDebtSettlement.ConsumerDebtSettlementKey.Find(consumerDebtSettlementId); consumerDebtSettlementRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // There is no need to generate a report on a settlement that isn't new. This will momentarily lock the status table so we // can see if the settlement is new. try { consumerDebtSettlementRow.StatusRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); if (consumerDebtSettlementRow.StatusRow.StatusCode != Status.New) { return; } } finally { consumerDebtSettlementRow.StatusRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // The RowVersion is needed to update the record with the new PDF report. mergeInfo.RowVersion = consumerDebtSettlementRow.RowVersion; // The negotiation row contains the link to the base matching row. consumerDebtNegotiationRow = consumerDebtSettlementRow.ConsumerDebtNegotiationRow; consumerDebtNegotiationRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // The base matching row is where we'll find the working order. matchRow = consumerDebtNegotiationRow.MatchRow; matchRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // The working order row is where the blotter can be found. workingOrderRow = matchRow.WorkingOrderRow; workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // And the blotter will lead us to the Entity hierarchy which is where we'll find the rules. blotterRow = workingOrderRow.BlotterRow; blotterRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // The 'Pending' status is applied to the Settlement after the letter has been generated. The status needs to be picked up while we're // still locking and reading tables. StatusRow statusRow = DataModel.Status.StatusKeyStatusCode.Find(Status.Pending); try { statusRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); mergeInfo.StatusId = statusRow.StatusId; } finally { statusRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } //Find the consumerDebt to find the credit Card to get the origianl creditor ConsumerTrustNegotiationRow trustNegotiation = null; MatchRow contraWorMatchRow = DataModel.Match.MatchKey.Find(matchRow.ContraMatchId); contraWorMatchRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { trustNegotiation = contraWorMatchRow.GetConsumerTrustNegotiationRows()[0]; } finally { if (contraWorMatchRow != null) { contraWorMatchRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } //Consumer Debt Row trustNegotiation.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { creditCardRow = trustNegotiation.CreditCardRow; } finally { if (trustNegotiation != null) { trustNegotiation.ReleaseReaderLock(dataModelTransaction.TransactionId); } } creditCardRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // There is only going to be one Debt Class record associated with this blotter, but the iteration is an easier construct to work with than // the equivalent array logic for a single element. foreach (DebtClassRow debtClassRow in blotterRow.GetDebtClassRows()) { // This variable will keep track of our current location as we crawl up the hierarchy. It is important to release the records as soon // as possible to reduce the likelyhood of a deadlock. DebtClassRow currentDebtClassRow = debtClassRow; DebtClassRow nextDebtClassRow = null; // This flag will be set when a Debt Class in the hierarchy contains a Debt Rule. Boolean isFound = false; // This will crawl up the hierarchy until a Debt Class is found with a rule. This rule will provide the opening values for the bid on // this negotiation. do { try { // This will lock the current item in the hierarchy so it can be examined for a rule or, failing that, a parent element. currentDebtClassRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // If the current Debt Class has no rule then the Entity Hierarchy is used to find the parent element. if (currentDebtClassRow.IsSettlementTemplateNull()) { // The entity is the root of all objects in the hierarchy. From this object the path to the parent Debt Class can be // navigated. EntityRow entityRow = DataModel.Entity.EntityKey.Find(currentDebtClassRow.DebtClassId); try { // Each entity needs to be locked before the relation can be used. entityRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // This will find each relation in the hierarchy which uses the current node as a child. foreach (EntityTreeRow entityTreeRow in entityRow.GetEntityTreeRowsByFK_Entity_EntityTree_ChildId()) { try { // Lock the relation down before navigating to the parent. entityTreeRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // This is the parent entity of the current entity in the crawl up the hierarchy. EntityRow parentEntityRow = entityTreeRow.EntityRowByFK_Entity_EntityTree_ParentId; try { // The parent entity must be locked befor it can be checked for blotters and then, in turn, debt // classes. parentEntityRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // In practice, there will be zero or one blotter rows. The iteration makes it easier to check both // conditions. foreach (BlotterRow parentBlotterRow in parentEntityRow.GetBlotterRows()) { try { // The blotter must be locked before iterating through the Debt Classes that may be associated // with the blotter. parentBlotterRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // Each blotter can have zero or one Debt Classes associated with it. This is a long an // tortuous way to finally get to the parent Debt Class. foreach (DebtClassRow parentDebtClassRow in parentBlotterRow.GetDebtClassRows()) { try { // Now that we've finally found the parent Debt Class, it will become the parent on the // next pass through the hierarchy. Note that the locks are released each time we pass // through a level of the hierarchy. parentDebtClassRow.AcquireReaderLock( dataModelTransaction.TransactionId, DataModel.LockTimeout); nextDebtClassRow = parentDebtClassRow; } finally { // The locks are released after the parent Debt Class is found. parentDebtClassRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } } finally { // The locks are released after the each level of the hierarchy is checked. parentBlotterRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } } finally { // The parent Entity record is released after each level of the hiearchy is examined for a parent. parentEntityRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } finally { // The relationship record is released after each level of the hierarchy is examined for a parent. entityTreeRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } } finally { // Finaly, the current entity is released. This allows us to finally move on to the next level of the hierarchy // without having to hold the locks for the entire transaction. entityRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } else { // The template has been found and converted back to a Microsoft Word template. mergeInfo.SourceDocument = Convert.FromBase64String(currentDebtClassRow.SettlementTemplate); // This will cause the loop to exit. isFound = true; } } finally { // The current Debt Class can be released. At this point, every record that was locked to read the hiearchy has been // released and the loop can either exit (when a rule is found) or move on to the parent Debt Class. currentDebtClassRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // Now that all the locks are released, the parent Debt Class becomes the current one for the next level up in the hierarchy. // This algorithm will keep on climbing through the levels until a rule is found or the hierarchy is exhausted. currentDebtClassRow = nextDebtClassRow; } while (isFound == false && currentDebtClassRow != null); } // AccountBalance mergeInfo.Dictionary.Add( "AccountBalance", String.Format("{0:$#,##0.00}", consumerDebtSettlementRow.AccountBalance)); // CreatedDate mergeInfo.Dictionary.Add("CreatedDate", consumerDebtSettlementRow.CreatedTime); // Original Creditor mergeInfo.Dictionary.Add( "DebtHolder", creditCardRow.IsDebtHolderNull() ? null : creditCardRow.DebtHolder); // DebtorAccountNumber mergeInfo.Dictionary.Add( "DebtorAccountNumber", consumerDebtSettlementRow.IsDebtorAccountNumberNull() ? null : consumerDebtSettlementRow.DebtorAccountNumber); // DebtorBankAccountNumber mergeInfo.Dictionary.Add( "DebtorBankAccountNumber", consumerDebtSettlementRow.IsDebtorBankAccountNumberNull() ? null : consumerDebtSettlementRow.DebtorBankAccountNumber); // DebtorBankRoutingNumber mergeInfo.Dictionary.Add( "DebtorBankRoutingNumber", consumerDebtSettlementRow.IsDebtorBankRoutingNumberNull() ? null : consumerDebtSettlementRow.DebtorBankRoutingNumber); // DebtorAddress1 mergeInfo.Dictionary.Add( "DebtorAddress1", consumerDebtSettlementRow.IsDebtorAddress1Null() ? null : consumerDebtSettlementRow.DebtorAddress1); // DebtorAddress2 mergeInfo.Dictionary.Add( "DebtorAddress2", consumerDebtSettlementRow.IsDebtorAddress2Null() ? null : consumerDebtSettlementRow.DebtorAddress2); // DebtorCity mergeInfo.Dictionary.Add( "DebtorCity", consumerDebtSettlementRow.IsDebtorCityNull() ? null : consumerDebtSettlementRow.DebtorCity); // DebtorFirstName mergeInfo.Dictionary.Add( "DebtorFirstName", consumerDebtSettlementRow.IsDebtorFirstNameNull() ? null : consumerDebtSettlementRow.DebtorFirstName); // DebtorLastName mergeInfo.Dictionary.Add( "DebtorLastName", consumerDebtSettlementRow.IsDebtorLastNameNull() ? null : consumerDebtSettlementRow.DebtorLastName); // DebtorMiddleName mergeInfo.Dictionary.Add( "DebtorMiddleName", consumerDebtSettlementRow.IsDebtorMiddleNameNull() ? null : consumerDebtSettlementRow.DebtorMiddleName); // DebtorOriginalAccountNumber mergeInfo.Dictionary.Add( "DebtorOriginalAccountNumber", consumerDebtSettlementRow.DebtorOriginalAccountNumber); // DebtorPostalCode mergeInfo.Dictionary.Add( "DebtorPostalCode", consumerDebtSettlementRow.IsDebtorPostalCodeNull() ? null : consumerDebtSettlementRow.DebtorPostalCode); // DebtorProvince String debtorProvince = null; if (!consumerDebtSettlementRow.IsDebtorProvinceIdNull()) { ProvinceRow provinceRow = consumerDebtSettlementRow.ProvinceRowByFK_Province_ConsumerDebtSettlement_DebtorProvinceId; try { provinceRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); debtorProvince = provinceRow.Abbreviation; } finally { provinceRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } mergeInfo.Dictionary.Add("DebtorProvinceAbbreviation", debtorProvince); // DebtorSalutation mergeInfo.Dictionary.Add( "DebtorSalutation", consumerDebtSettlementRow.IsDebtorSalutationNull() ? null : consumerDebtSettlementRow.DebtorSalutation); // DebtorSuffix mergeInfo.Dictionary.Add( "DebtorSuffix", consumerDebtSettlementRow.IsDebtorSuffixNull() ? null : consumerDebtSettlementRow.DebtorSuffix); // PayeeAddress1 mergeInfo.Dictionary.Add( "PayeeAddress1", consumerDebtSettlementRow.IsPayeeAddress1Null() ? null : consumerDebtSettlementRow.PayeeAddress1); // PayeeAddress2 mergeInfo.Dictionary.Add( "PayeeAddress2", consumerDebtSettlementRow.IsPayeeAddress2Null() ? null : consumerDebtSettlementRow.PayeeAddress2); // PayeeCity mergeInfo.Dictionary.Add( "PayeeCity", consumerDebtSettlementRow.IsPayeeCityNull() ? null : consumerDebtSettlementRow.PayeeCity); // PayeeCompanyName mergeInfo.Dictionary.Add( "PayeeCompanyName", consumerDebtSettlementRow.IsPayeeCompanyNameNull() ? null : consumerDebtSettlementRow.PayeeCompanyName); // PayeeContactName mergeInfo.Dictionary.Add( "PayeeContactName", consumerDebtSettlementRow.IsPayeeContactNameNull() ? null : consumerDebtSettlementRow.PayeeContactName); // PayeeDepartment mergeInfo.Dictionary.Add( "PayeeDepartment", consumerDebtSettlementRow.IsPayeeDepartmentNull() ? null : consumerDebtSettlementRow.PayeeDepartment); // PayeeEmail mergeInfo.Dictionary.Add( "PayeeEmail", consumerDebtSettlementRow.IsPayeeEmailNull() ? null : consumerDebtSettlementRow.PayeeEmail); // PayeeFax mergeInfo.Dictionary.Add( "PayeeFax", consumerDebtSettlementRow.IsPayeeFaxNull() ? null : consumerDebtSettlementRow.PayeeFax); // PayeeForBenefitOf mergeInfo.Dictionary.Add( "PayeeForBenefitOf", consumerDebtSettlementRow.IsPayeeForBenefitOfNull() ? null : consumerDebtSettlementRow.PayeeForBenefitOf); // PayeePhone mergeInfo.Dictionary.Add( "PayeePhone", consumerDebtSettlementRow.IsPayeePhoneNull() ? null : consumerDebtSettlementRow.PayeePhone); // PayeePostalCode mergeInfo.Dictionary.Add( "PayeePostalCode", consumerDebtSettlementRow.IsPayeePostalCodeNull() ? null : consumerDebtSettlementRow.PayeePostalCode); // PayeeProvince String payeeProvince = null; if (!consumerDebtSettlementRow.IsPayeeProvinceIdNull()) { ProvinceRow provinceRow = consumerDebtSettlementRow.ProvinceRowByFK_Province_ConsumerDebtSettlement_PayeeProvinceId; try { provinceRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); payeeProvince = provinceRow.Abbreviation; } finally { provinceRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } mergeInfo.Dictionary.Add("PayeeProvinceAbbreviation", payeeProvince); // PaymentLength mergeInfo.Dictionary.Add("PaymentLength", consumerDebtSettlementRow.PaymentLength); // PaymentStartDate mergeInfo.Dictionary.Add("PaymentStartDate", consumerDebtSettlementRow.PaymentStartDate.ToLocalTime().ToLongDateString()); // PayeeBankAccountNumber mergeInfo.Dictionary.Add( "PayeeBankAccountNumber", consumerDebtSettlementRow.IsPayeeBankAccountNumberNull() ? null : consumerDebtSettlementRow.PayeeBankAccountNumber); // PayeeBankRoutingNumber mergeInfo.Dictionary.Add( "PayeeBankRoutingNumber", consumerDebtSettlementRow.IsPayeeBankRoutingNumberNull() ? null : consumerDebtSettlementRow.PayeeBankRoutingNumber); // SettlementAmount mergeInfo.Dictionary.Add("SettlementAmount", consumerDebtSettlementRow.SettlementAmount); // SettlementPercent mergeInfo.Dictionary.Add("SettlementPercent", consumerDebtSettlementRow.SettlementAmount / consumerDebtSettlementRow.AccountBalance); // TermPaymentAmount mergeInfo.Dictionary.Add("TermPaymentAmount", consumerDebtSettlementRow.SettlementAmount / consumerDebtSettlementRow.PaymentLength); // The payment methods is modeled as a vector which makes it difficult to add as a single merge field. To work around this, each of the // possible payment methods are described in the data dictionary using the form 'Is{PaymentMethodName}'. The Word Merge process should look for // the presence of these fields to generate a block of text for the instructions for each of the payment methods. This iteration will // collect all the possible payment method types in an array and assume that they don't exist (i.e. set them to a Boolean value of 'false') // until they're found in the settlement instructions. foreach (PaymentMethodTypeRow paymentMethodTypeRow in DataModel.PaymentMethodType) { try { paymentMethodTypeRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); mergeInfo.Dictionary.Add(String.Format("Is{0}", paymentMethodTypeRow.Name.Replace(" ", String.Empty)), false); } finally { paymentMethodTypeRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } // This iteration will cycle through all the payment methods in the settlement and set them to be true. The result is a dictionary of all // possible payment methods with the ones included in this settlement set to be the Boolean value of 'true'. foreach (ConsumerDebtSettlementPaymentMethodRow consumerDebtSettlementPaymentMethodRow in consumerDebtSettlementRow.GetConsumerDebtSettlementPaymentMethodRows()) { try { // Each of the payment methods in the settlement are modeled as a list that is associated with the settlement. This will lock each // of the items in the list in turn and examine the parent 'PaymentMethodType' record to construct a mail-merge tag. consumerDebtSettlementPaymentMethodRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); PaymentMethodTypeRow paymentMethodTypeRow = DataModel.PaymentMethodType.PaymentMethodTypeKey.Find( consumerDebtSettlementPaymentMethodRow.PaymentMethodTypeId); try { // Once the parent MethodType is found a tag is added to the dictionary. The presence of the 'Is<PaymentMethodType>' item in // the dictionary means that the given payment method is acceptable for this settlement. paymentMethodTypeRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); mergeInfo.Dictionary[String.Format("Is{0}", paymentMethodTypeRow.Name.Replace(" ", String.Empty))] = true; } finally { // The parent payment method type row is not needed any longer. paymentMethodTypeRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } finally { // Release the payment method row. consumerDebtSettlementPaymentMethodRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } } finally { // The CreditCardRow is no longer needed. if (creditCardRow != null && creditCardRow.IsReaderLockHeld(dataModelTransaction.TransactionId)) { creditCardRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // The ConsumerDebtSettlementRow is no longer needed. if (consumerDebtSettlementRow != null) { consumerDebtSettlementRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // The ConsumerDebtNegotiation Row is no longer needed. if (consumerDebtNegotiationRow != null) { consumerDebtNegotiationRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // The MatchRow is no longer needed. if (matchRow != null) { matchRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // The WorkingOrderRow is no longer needed. if (workingOrderRow != null) { workingOrderRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // The BlotterRow is no longer needed. if (blotterRow != null) { blotterRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } MemoryStream memoryStream = null; try { // At this point, all the data has been collected and the record locks released. It is time to merge the document. memoryStream = SettlementDocumentFactory.iMailMerge.CreateDocument(mergeInfo.SourceDocument, mergeInfo.Dictionary); } catch (Exception exception) { EventLog.Error("There was a problem creating the settlement letter. \n Details: {0}, {1}", exception.Message, exception.StackTrace); } if (memoryStream != null) { // Update the settlement with the newly generated PFD file. dataModel.UpdateConsumerDebtSettlement( null, null, null, null, new Object[] { consumerDebtSettlementId }, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, mergeInfo.RowVersion, null, Convert.ToBase64String(memoryStream.ToArray()), mergeInfo.StatusId); } // If we reached here the transaction was successful. transactionScope.Complete(); } }
/// <summary> /// If a matching consumer debt record already exists, update the account with this, rather than creating a new one. /// </summary> /// <param name="entity">The entity row of the consumer debt record.</param> /// <returns>The entityId.</returns> internal Guid Update(EntityRow entity) { DataModel dataModel = new DataModel(); DataModelTransaction dataModelTransaction = DataModelTransaction.Current; CountryRow country; Guid countryId; Guid? provinceId = null; EntityRow dollars; Guid dollarsId; ConsumerRow consumer; ConsumerDebtRow consumerDebt; CreditCardRow creditCard; SecurityRow security; WorkingOrderRow workingOrder; Guid consumerId; Guid consumerDebtId = entity.EntityId; Guid creditCardId; Guid entityId = entity.EntityId; Guid securityId = entity.EntityId; Guid workingOrderId; Int64 consumerVersion; Int64 consumerDebtVersion; Int64 creditCardVersion; Int64 entityVersion = entity.RowVersion; Int64 securityVersion; Int64 workingOrderVersion; Boolean updateConsumer = false; Boolean updateConsumerDebt = false; Boolean updateCreditCard = false; Boolean updateEntity = false; Boolean updateSecurity = false; DateTime currentUTCTime = DateTime.UtcNow; entity.ReleaseReaderLock(dataModelTransaction.TransactionId); // We need write access to the containing blotter in order to add a record to it. if (!DataModelFilters.HasAccess(dataModelTransaction, TradingSupport.UserId, this.Record.Blotter, AccessRight.Write)) { throw new SecurityException("Current user does not have write access to the selected blotter"); } #if false // We need write access to the consumer debt's entity in order to update it. if (!TradingSupport.HasAccess(dataModelTransaction, entityId, AccessRight.Write)) { throw new SecurityException("Current user does not have write access to the selected consumer debt"); } #endif // Via the country row... country = TradingSupport.FindCountryByKey( this.Record.ConfigurationId, "FK_Country_Security", new object[] { this.Record.CountryCode }); countryId = country.CountryId; country.ReleaseReaderLock(dataModelTransaction.TransactionId); // ... get the province Id. if (this.Record.ProvinceCode != null) { ProvinceRow province = TradingSupport.FindProvinceByKey( this.Record.ConfigurationId, "FK_Province_Consumer", new object[] { this.Record.ProvinceCode }); provinceId = province.ProvinceId; province.ReleaseReaderLock(dataModelTransaction.TransactionId); } // Get the USD security Id. dollars = TradingSupport.FindEntityByKey( this.Record.ConfigurationId, "FK_Security_WorkingOrder_SettlementId", new object[] { this.Record.Currency }); dollarsId = dollars.EntityId; dollars.ReleaseReaderLock(dataModelTransaction.TransactionId); // See if the entity needs to be updated. entity.AcquireReaderLock(dataModelTransaction); entityVersion = entity.RowVersion; if (TradingSupport.IsColumnOld(entity, "Name", this.Record.OriginalAccountNumber)) { updateEntity = true; } entity.ReleaseLock(dataModelTransaction.TransactionId); // Get security's children see if we need to update securityRow. security = DataModel.Security.SecurityKey.Find(entityId); security.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); securityVersion = security.RowVersion; consumerDebt = DataModel.ConsumerDebt.ConsumerDebtKey.Find(security.SecurityId); workingOrder = security.GetWorkingOrderRowsByFK_Security_WorkingOrder_SecurityId()[0]; if (TradingSupport.IsColumnOld(security, "CountryId", countryId)) { updateSecurity = true; } security.ReleaseLock(dataModelTransaction.TransactionId); // Control the working order: workingOrder.AcquireWriterLock(dataModelTransaction); // Get the consumer debt's children and see if we need to update the consumer debt. consumerDebt.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); consumerDebtVersion = consumerDebt.RowVersion; creditCard = DataModel.CreditCard.CreditCardKey.Find(consumerDebt.CreditCardId); if (TradingSupport.IsColumnOld(consumerDebt, "DateOfDelinquency", this.Record.DateOfDelinquency) || TradingSupport.IsColumnOld(consumerDebt, "Representative", this.Record.Representative) || TradingSupport.IsColumnOld(consumerDebt, "Tag", this.Record.Tag)) { updateConsumerDebt = true; } consumerDebt.ReleaseLock(dataModelTransaction.TransactionId); creditCard.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); creditCardId = creditCard.CreditCardId; creditCardVersion = creditCard.RowVersion; consumer = DataModel.Consumer.ConsumerKey.Find(creditCard.ConsumerId); if (TradingSupport.IsColumnOld(creditCard, "AccountBalance", this.Record.AccountBalance) || TradingSupport.IsColumnOld(creditCard, "AccountNumber", this.Record.AccountCode) || TradingSupport.IsColumnOld(creditCard, "DebtHolder", this.Record.DebtHolder) || TradingSupport.IsColumnOld(creditCard, "OriginalAccountNumber", this.Record.OriginalAccountNumber)) { updateCreditCard = true; } creditCard.ReleaseLock(dataModelTransaction.TransactionId); consumer.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); consumerId = consumer.ConsumerId; consumerVersion = consumer.RowVersion; if (TradingSupport.IsColumnOld(consumer, "Address1", this.Record.Address1) || TradingSupport.IsColumnOld(consumer, "Address2", this.Record.Address2) || TradingSupport.IsColumnOld(consumer, "City", this.Record.City) || TradingSupport.IsColumnOld(consumer, "DateOfBirth", this.Record.DateOfBirth) || TradingSupport.IsColumnOld(consumer, "FirstName", this.Record.FirstName) || TradingSupport.IsColumnOld(consumer, "LastName", this.Record.LastName) || TradingSupport.IsColumnOld(consumer, "PostalCode", this.Record.PostalCode) || TradingSupport.IsColumnOld(consumer, "MiddleName", this.Record.MiddleName) || TradingSupport.IsColumnOld(consumer, "PhoneNumber", this.Record.PhoneNumber) || TradingSupport.IsColumnOld(consumer, "ProvinceId", provinceId) || TradingSupport.IsColumnOld(consumer, "SocialSecurityNumber", this.Record.SocialSecurityNumber) || TradingSupport.IsColumnOld(consumer, "Suffix", this.Record.Suffix)) { updateConsumer = true; } consumer.ReleaseLock(dataModelTransaction.TransactionId); workingOrder.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); workingOrderId = workingOrder.WorkingOrderId; workingOrderVersion = workingOrder.RowVersion; //workingOrder.ReleaseLock(dataModelTransaction.TransactionId); // We need write access to the containing blotter in order to add a record to it. if (!TradingSupport.HasAccess(dataModelTransaction, PersistenceHelper.GetBlotterForConsumer(dataModelTransaction, consumerId), AccessRight.Write)) { throw new SecurityException("Current user does not have write access to the original blotter"); } if (updateConsumer) { dataModel.UpdateConsumer( this.Record.Address1 != null ? (object)this.Record.Address1 : DBNull.Value, this.Record.Address2 != null ? (object)this.Record.Address2 : DBNull.Value, null, null, this.Record.City != null ? (object)this.Record.City : DBNull.Value, consumerId, new object[] { consumerId }, this.Record.DateOfBirth != null ? (object)this.Record.DateOfBirth.Value : DBNull.Value, null, null, this.Record.FirstName != null ? (object)this.Record.FirstName : DBNull.Value, null, this.Record.LastName != null ? (object)this.Record.LastName : DBNull.Value, this.Record.MiddleName != null ? (object)this.Record.MiddleName : DBNull.Value, this.Record.PhoneNumber != null ? (object)StringUtilities.CleanUpAlphaNumericString(this.Record.PhoneNumber) : DBNull.Value, this.Record.PostalCode != null ? (object)this.Record.PostalCode : DBNull.Value, provinceId, consumerVersion, null, StringUtilities.CleanUpAlphaNumericString(this.Record.SocialSecurityNumber), this.Record.Suffix != null ? (object)this.Record.Suffix : DBNull.Value); } if (updateConsumerDebt) { dataModel.UpdateConsumerDebt( null, consumerDebtId, new object[] { consumerDebtId }, null, null, this.Record.DateOfDelinquency != null ? (object)this.Record.DateOfDelinquency.Value : DBNull.Value, null, null, this.Record.Representative != null ? (object)this.Record.Representative : DBNull.Value, consumerDebtVersion, this.Record.Tag != null ? (object)this.Record.Tag : DBNull.Value, null, null); } if (updateCreditCard) { dataModel.UpdateCreditCard( this.Record.AccountBalance, this.Record.AccountCode, null, creditCardId, new object[] { creditCardId }, this.Record.DebtHolder, null, null, null, StringUtilities.CleanUpAlphaNumericString(this.Record.OriginalAccountNumber), creditCardVersion, null); } if (updateEntity) { dataModel.UpdateEntity( null, null, entityId, new object[] { entityId }, null, null, null, null, null, null, null, null, null, null, null, currentUTCTime, this.Record.OriginalAccountNumber, entityVersion, null, null); } if (updateSecurity) { dataModel.UpdateSecurity( null, countryId, null, null, null, 1, 1, securityVersion, securityId, new object[] { securityId }, null, null, null); } dataModel.UpdateWorkingOrder( null, this.Record.Blotter, null, null, null, null, null, null, null, null, null, null, null, currentUTCTime, TradingSupport.UserId, null, workingOrderVersion, null, null, dollarsId, null, null, null, null, null, null, null, null, null, currentUTCTime, workingOrderId, new object[] { workingOrderId }); return(entityId); }