/// <summary> /// See if the ChildId is a ParentId to stop circular reference. /// </summary> /// <param name="transaction"></param> /// <param name="parent"></param> /// <param name="childId"></param> /// <returns></returns> private bool IsParentEntity(DataModelTransaction transaction, EntityRow parent, Guid childId) { parent.AcquireReaderLock(transaction.TransactionId, DataModel.LockTimeout); try { //Make sure we are not adding a child element that is also a parent. foreach (EntityTreeRow entityRow in parent.GetEntityTreeRowsByFK_Entity_EntityTree_ChildId()) { entityRow.AcquireWriterLock(transaction.TransactionId, DataModel.LockTimeout); try { if (entityRow.ParentId == childId) { return(true); } if (IsParentEntity(transaction, entityRow.EntityRowByFK_Entity_EntityTree_ParentId, childId) == true) { return(true); } } finally { entityRow.ReleaseLock(transaction.TransactionId); } } } finally { parent.ReleaseReaderLock(transaction.TransactionId); } return(false); }
public ConsumerTrustMatchInfo(DataModelTransaction dataModelTransaction, WorkingOrderRow workingOrderRow) { // Initialize the object this.PaymentMethodTypes = new List <Guid>(); // These rows are required for navigating through the asset. Locks are acquired temporarily for them and released as soon as the Consumer Debt // information is collected. BlotterRow blotterRow = null; SecurityRow securityRow = null; ConsumerTrustRow consumerTrustRow = null; // The working order row is where everything starts. This is the asset that is to be matched against another. workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { // The working order identifier is used when creating matches. this.WorkingOrderId = workingOrderRow.WorkingOrderId; // The underlying security is a Consumer Debt record. securityRow = workingOrderRow.SecurityRowByFK_Security_WorkingOrder_SecurityId; // The blotter row needs to be examined for this cross. blotterRow = workingOrderRow.BlotterRow; } finally { workingOrderRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } securityRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { consumerTrustRow = securityRow.GetConsumerTrustRows()[0]; } finally { securityRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } DebtRuleRow debtRuleRow = null; // This row contains the actual asset that is to be matched. consumerTrustRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { // The savings balance is used when calculating the status of the match (whether there is enough funds in the account). this.SavingsBalance = consumerTrustRow.SavingsBalance; if (consumerTrustRow.IsDebtRuleIdNull() == false) { // At this point a rule override was found on an asset and there's no need to search the Entity hierarchy. debtRuleRow = consumerTrustRow.DebtRuleRow; } } finally { consumerTrustRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // This will attempt to find the debt rule associated with this credit card. If there is no rule explicitly associated with the asset, // then the entity hierarchy is searched until a debt class is found that contains a rule. When the rule is found, the values in that rule // will become the opening bid. DebtClassRow[] blotterRowDebtClassRows = null; blotterRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { // The blotter is required for adding matches to the data model once they are found. this.BlotterId = blotterRow.BlotterId; if (debtRuleRow == null) { blotterRowDebtClassRows = blotterRow.GetDebtClassRows(); } } finally { blotterRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } if (debtRuleRow == null) { // At this point, the asset hasn't got an explicit rule, so the hierarchy will need to be searched. There is only going to be one Debt // Class element 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 blotterRowDebtClassRows) { // This variable will keep track of our current location as we crawl up the hierarchy. DebtClassRow currentDebtClassRow = debtClassRow; DebtClassRow nextDebtClassRow = null; // 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 { // 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); Guid currentDebtClassRowDebtClassId; try { currentDebtClassRowDebtClassId = currentDebtClassRow.DebtClassId; if (currentDebtClassRow.IsDebtRuleIdNull() == false) { // At this point we have finally found a debt rule that can be used to start the negotiations. debtRuleRow = currentDebtClassRow.DebtRuleRow; } } 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); } // If the current Debt Class has no rule then the Entity Hierarchy is used to find the parent element. if (debtRuleRow == null) { EntityTreeRow[] entityTreeRows; // 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(currentDebtClassRowDebtClassId); // Each entity needs to be locked before the relation can be used. entityRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { entityTreeRows = entityRow.GetEntityTreeRowsByFK_Entity_EntityTree_ChildId(); } 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); } // This will find each relation in the hierarchy which uses the current node as a child. foreach (EntityTreeRow entityTreeRow in entityTreeRows) { EntityRow parentEntityRow; // Lock the relation down before navigating to the parent. entityTreeRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { // This is the parent entity of the current entity in the crawl up the hierarchy. parentEntityRow = entityTreeRow.EntityRowByFK_Entity_EntityTree_ParentId; } finally { // The relationship record is released after each level of the hierarchy is examined for a parent. entityTreeRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } BlotterRow[] blotterRows; // 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); try { blotterRows = parentEntityRow.GetBlotterRows(); } finally { // The parent Entity record is released after each level of the hiearchy is examined for a parent. parentEntityRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // In practice, there will be zero or one blotter rows. The iteration makes it easier to check both // conditions. foreach (BlotterRow parentBlotterRow in blotterRows) { DebtClassRow[] parentBlotterRowDebtClassRows; // The blotter must be locked before iterating through the Debt Classes that may be associated // with the blotter. parentBlotterRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { parentBlotterRowDebtClassRows = parentBlotterRow.GetDebtClassRows(); } finally { // The locks are released after the each level of the hierarchy is checked. parentBlotterRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // 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 parentBlotterRowDebtClassRows) { // 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. nextDebtClassRow = parentDebtClassRow; } } } } // 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(debtRuleRow == null && currentDebtClassRow != null); } } // The data is copied out of the Debt Rule when one is found in the Entity hierarchy. if (debtRuleRow != null) { DebtRulePaymentMethodRow[] debtRuleRowDebtRulePaymentMethodRows; // At this point we found a rule that can be used for the opening bid of a settlement. debtRuleRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { // These values are used in the opening bid of a negotiated settlement. this.PaymentLength = debtRuleRow.PaymentLength; this.PaymentStartDateLength = debtRuleRow.PaymentStartDateLength; this.PaymentStartDateUnitId = debtRuleRow.PaymentStartDateUnitId; this.SettlementValue = debtRuleRow.SettlementValue; this.SettlementUnitId = debtRuleRow.SettlementUnitId; debtRuleRowDebtRulePaymentMethodRows = debtRuleRow.GetDebtRulePaymentMethodRows(); } finally { // A lock on the Debt Rule is no longer required. debtRuleRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // This will copy each of the payment methods that are available for this settlement. Note that the table rows are locked only long // enough to acquire the item, then released. foreach (DebtRulePaymentMethodRow debtRulePaymentMethodRow in debtRuleRowDebtRulePaymentMethodRows) { debtRulePaymentMethodRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { this.PaymentMethodTypes.Add(debtRulePaymentMethodRow.PaymentMethodTypeId); } finally { debtRulePaymentMethodRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } } }
/// <summary> /// Create a new Debt Holder Record /// </summary> /// <returns></returns> internal Guid Create(EntityRow existingTrust, CreditCardRow existingCard) { DataModelTransaction dataModelTransaction = DataModelTransaction.Current; DataModel dataModel = new DataModel(); Guid tenantId = PersistenceHelper.GetTenantForEntity(dataModelTransaction, this.Record.Blotter); Guid consumerId; Guid entityId = Guid.Empty; Guid existingCardId = Guid.Empty; Int64 existingCardVersion = 0; Boolean updateExistingCard = false; if (existingTrust != null) { entityId = existingTrust.EntityId; existingTrust.ReleaseReaderLock(dataModelTransaction.TransactionId); } if (existingCard != null) { existingCard.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); existingCardId = existingCard.CreditCardId; existingCardVersion = existingCard.RowVersion; if (TradingSupport.IsColumnOld(existingCard, "AccountBalance", this.Record.AccountBalance) || TradingSupport.IsColumnOld(existingCard, "AccountNumber", this.Record.AccountCode) || TradingSupport.IsColumnOld(existingCard, "DebtHolder", this.Record.DebtHolder) || TradingSupport.IsColumnOld(existingCard, "OriginalAccountNumber", this.Record.OriginalAccountNumber)) { updateExistingCard = true; } } // We need write access to the containing blotter in order to add a record to it. if (!TradingSupport.HasAccess(dataModelTransaction, this.Record.Blotter, AccessRight.Write)) { throw new SecurityException("Current user does not have write access to the selected blotter"); } #if false if (existingTrust != null && !TradingSupport.HasAccess(dataModelTransaction, entityId, AccessRight.Write)) { throw new SecurityException("Current user does not have write access to the selected consumer"); } #endif if (existingTrust == null) { consumerId = this.CreateConsumer(); } else { consumerId = this.UpdateConsumer(existingTrust); } if (existingCard == null) { dataModel.CreateCreditCard( this.Record.AccountBalance, this.Record.AccountCode, consumerId, Guid.NewGuid(), this.Record.DebtHolder, null, null, this.Record.AccountCode, StringUtilities.CleanUpAlphaNumericString(this.Record.OriginalAccountNumber), tenantId); } else if (updateExistingCard) { dataModel.UpdateCreditCard( this.Record.AccountBalance, this.Record.AccountCode, null, existingCardId, new object[] { existingCardId }, this.Record.DebtHolder, null, null, this.Record.AccountCode, StringUtilities.CleanUpAlphaNumericString(this.Record.OriginalAccountNumber), existingCardVersion, null); } return(consumerId); }
/// <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); }