/// <summary> /// Collect negotiation and settlement data to move /// </summary> /// <param name="dataModelTransaction"></param> /// <param name="moveToInfo"></param> /// <param name="matchRow"></param> private static void CollectNegotiationData(DataModelTransaction dataModelTransaction, MoveToInfo moveToInfo, MatchRow matchRow) { foreach (ConsumerTrustNegotiationRow consumerTrustNegotiationRow in matchRow.GetConsumerTrustNegotiationRows()) { consumerTrustNegotiationRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { moveToInfo.consumerTrustNegotiationRows.Add(new BaseRecord() { RowId = consumerTrustNegotiationRow.ConsumerTrustNegotiationId, RowVersion = consumerTrustNegotiationRow.RowVersion }); //Get counter payment rows to move foreach (ConsumerTrustNegotiationCounterPaymentMethodRow counterPaymentMethodRow in consumerTrustNegotiationRow.GetConsumerTrustNegotiationCounterPaymentMethodRows()) { counterPaymentMethodRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { moveToInfo.consumerTrustNegotiationCounterPaymentMethodRows.Add(new BaseRecord() { RowId = counterPaymentMethodRow.ConsumerTrustNegotiationCounterPaymentMethodId, RowVersion = counterPaymentMethodRow.RowVersion }); } finally { counterPaymentMethodRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } //Get offer payment rows to move foreach (ConsumerTrustNegotiationOfferPaymentMethodRow offerPaymentMethodRow in consumerTrustNegotiationRow.GetConsumerTrustNegotiationOfferPaymentMethodRows()) { offerPaymentMethodRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { moveToInfo.consumerTrustNegotiationOfferPaymentMethodRows.Add(new BaseRecord() { RowId = offerPaymentMethodRow.ConsumerTrustNegotiationOfferPaymentMethodId, RowVersion = offerPaymentMethodRow.RowVersion }); } finally { offerPaymentMethodRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } //Get Settlement rows to move CollectSettlementData(dataModelTransaction, moveToInfo, consumerTrustNegotiationRow); } finally { consumerTrustNegotiationRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } }
/// <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> /// Determine whether we can automatically accept this negotiation and do so if possible. /// </summary> /// <param name="info">The notification information.</param> private void TryAutoSettle(NotificationInfo info) { Guid? negotiationId = null; lock (DataModel.SyncRoot) { MatchRow matchRow = DataModel.Match.MatchKey.Find(info.MatchId); DebtClassRow debtClassRow; Guardian.Windows.DebtRule debtRuleRow; if (matchRow == null) { EventLog.Information("Match ('{0}') has been deleted before it could be auto-settled from the trust side", info.MatchId); return; } debtClassRow = DataModel.DebtClass.DebtClassKey.Find(matchRow.WorkingOrderRow.BlotterId); if (debtClassRow == null) { EventLog.Warning( "The debt class that the match ('{0}') was in has been deleted before it could be auto-settled from the trust side", info.MatchId); return; } debtRuleRow = Guardian.Windows.DebtClass.GetDebtRule(debtClassRow.DebtClassId, debtClassRow.BlotterRow.EntityRow.TypeId); if (debtRuleRow == null) { EventLog.Error("Debt class ('{0}') or its parents does not have a debt rule", debtClassRow.DebtClassId); return; } if (debtRuleRow.IsAutoSettled) { ConsumerTrustNegotiationRow[] negotiationRows = matchRow.GetConsumerTrustNegotiationRows(); ConsumerTrustNegotiationRow negotiationRow; // If, for whatever reason, there isn't exactly one negotiation row, log the error and bail. if (negotiationRows.Length < 1) { EventLog.Error("Consumer trust match ('{0}') has no negotiation rows", info.MatchId, negotiationRows.Length); return; } negotiationRow = negotiationRows[0]; foreach (ConsumerTrustNegotiationRow row in negotiationRows) if (row.RowVersion > negotiationRow.RowVersion) negotiationRow = row; if (IsCashValueAcceptable( negotiationRow.AccountBalance, negotiationRow.CounterSettlementValue, DataModel.SettlementUnit.SettlementUnitKey.Find(negotiationRow.CounterSettlementUnitId).SettlementUnitCode, negotiationRow.OfferSettlementValue, DataModel.SettlementUnit.SettlementUnitKey.Find(debtRuleRow.SettlementUnitId).SettlementUnitCode) && IsPaymentLengthAcceptable( negotiationRow.CounterPaymentLength, debtRuleRow.PaymentLength) && IsPaymentStartDateAcceptable( negotiationRow.CounterPaymentStartDateLength, negotiationRow.CounterPaymentStartDateUnitId, negotiationRow.OfferPaymentStartDateLength, negotiationRow.OfferPaymentStartDateUnitId) && ArePaymentTypesAcceptable( negotiationRow.GetConsumerTrustNegotiationCounterPaymentMethodRows(), negotiationRow.GetConsumerTrustNegotiationOfferPaymentMethodRows())) { negotiationId = negotiationRow.ConsumerTrustNegotiationId; } } } if (negotiationId != null) this.AcceptSettlement(negotiationId.Value); }
/// <summary> /// Update a Consumer Trust Negotiation Record. /// </summary> internal static void Update(ConsumerTrustNegotiationInfo[] consumerTrustNegotiations) { // An instance of the shared data model is required to use its methods. DataModel dataModel = new DataModel(); // The business logic requires the current time and the user identifier for auditing. Guid createUserId = TradingSupport.UserId; DateTime createDateTime = DateTime.UtcNow; DateTime modifiedTime = createDateTime; Guid modifiedUserId = createUserId; // This Web Method comes with an implicit transaction that is linked to its execution. DataModelTransaction dataModelTransaction = DataModelTransaction.Current; // This method can handle a batch of updates in a single transaction. foreach (ConsumerTrustNegotiationInfo consumerTrustNegotiationInfo in consumerTrustNegotiations) { // The payment methods available to this negotiation is a vector. Rather than delete everything and re-add it anytime an update is made, a // list of changes is constructed: new payment methods are added, obsolete payment methods are deleted and the ones that haven't changed are // left alone. These list help to work out the differences. List <ConsumerTrustNegotiationPaymentMethodTypeInfo> counterItems = new List <ConsumerTrustNegotiationPaymentMethodTypeInfo>(); // The blotter is not passed in from the client but is used Guid blotterId = Guid.Empty; TrustNegotiationInfo trustNegotiationInfo = null; // This is the next negotiation in the batch to be updated. ConsumerTrustNegotiationRow consumerTrustNegotiationRow = DataModel.ConsumerTrustNegotiation.ConsumerTrustNegotiationKey.Find(consumerTrustNegotiationInfo.ConsumerTrustNegotiationId); Guid matchId = Guid.Empty; Int64 originalVersion = Int64.MinValue; // Lock the current negotation record for reading. The data model doesn't support reader lock promotion, so the programming model is to // lock the database, collect the data, release the locks and then write. This model is especially important when iterating through a // large batch to prevent the number of locks from growing to large. consumerTrustNegotiationRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { matchId = consumerTrustNegotiationRow.MatchId; originalVersion = consumerTrustNegotiationRow.Version; } finally { consumerTrustNegotiationRow.ReleaseReaderLock(dataModelTransaction.TransactionId); consumerTrustNegotiationRow = null; } //Determine the most recent Negotiation to grab the counter payment methods. Int64 maxVersion = Int64.MinValue; MatchRow matchRow = DataModel.Match.MatchKey.Find(matchId); matchRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { foreach (ConsumerTrustNegotiationRow versionRow in matchRow.GetConsumerTrustNegotiationRows()) { try { versionRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); if (versionRow.Version > maxVersion) { maxVersion = versionRow.Version; consumerTrustNegotiationRow = versionRow; } } finally { versionRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } } finally { matchRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } consumerTrustNegotiationRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); try { //Check for rowversion if (originalVersion != consumerTrustNegotiationRow.Version) { throw new global::System.ServiceModel.FaultException <FluidTrade.Core.OptimisticConcurrencyFault>( new global::FluidTrade.Core.OptimisticConcurrencyFault("ConsumerTrustNegotiation", new object[] { consumerTrustNegotiationInfo.ConsumerTrustNegotiationId }), new FaultReason("Negotiation is busy. Please try again!")); } // The blotter identifier is used for access control and is not passed in by the client. blotterId = consumerTrustNegotiationRow.BlotterId; trustNegotiationInfo = new TrustNegotiationInfo(consumerTrustNegotiationRow); // Determine whether the client has the right to modify this record. if (!TradingSupport.HasAccess(dataModelTransaction, blotterId, AccessRight.Write)) { throw new FaultException <FluidTrade.Core.SecurityFault>(new SecurityFault("You do not have write access to the selected object.")); } // The payment methods are maintained as a vector associated with the negotiation record. This will lock each of the records and read the // payment methods into a data structure so the locks don't need to be held when it is time to write foreach (var consumerTrustNegotiationOfferPaymentMethodRow in consumerTrustNegotiationRow.GetConsumerTrustNegotiationCounterPaymentMethodRows()) { try { // Temporarily lock the record containing the payment method. consumerTrustNegotiationOfferPaymentMethodRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // This list is used to delete the payment methods that are no longer part of this negotiation. counterItems.Add( new ConsumerTrustNegotiationPaymentMethodTypeInfo( consumerTrustNegotiationOfferPaymentMethodRow.PaymentMethodTypeId, consumerTrustNegotiationOfferPaymentMethodRow.ConsumerTrustNegotiationCounterPaymentMethodId, consumerTrustNegotiationOfferPaymentMethodRow.RowVersion)); } finally { // At this point the payment method isn't needed. consumerTrustNegotiationOfferPaymentMethodRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } } finally { // At this point, the negotiation record isn't needed. It is critical to release the reader locks before attempting a write. consumerTrustNegotiationRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // At this point, all the data for this operation has been collected and the CRUD operations can be invoked to finish the update. Note that // the counter party information is not modified here, but is done through the Chinese wall. Guid newNegotiationId = Guid.NewGuid(); dataModel.CreateConsumerTrustNegotiation( trustNegotiationInfo.AccountBalance, trustNegotiationInfo.BlotterId, newNegotiationId, trustNegotiationInfo.CounterPaymentLength, trustNegotiationInfo.CounterPaymentStartDateLength, trustNegotiationInfo.CounterPaymentStartDateUnitId, trustNegotiationInfo.CounterSettlementUnitId, trustNegotiationInfo.CounterSettlementValue, createDateTime, createUserId, trustNegotiationInfo.CreditCardId, trustNegotiationInfo.IsRead, trustNegotiationInfo.IsReply, trustNegotiationInfo.MatchId, modifiedTime, modifiedUserId, consumerTrustNegotiationInfo.PaymentLength, consumerTrustNegotiationInfo.PaymentStartDateLength, consumerTrustNegotiationInfo.PaymentStartDateUnitId, consumerTrustNegotiationInfo.SettlementUnitId, consumerTrustNegotiationInfo.SettlementValue, consumerTrustNegotiationInfo.StatusId, out trustNegotiationInfo.Version); // This will add the payment methods to the negotiation that are not already there. foreach (Guid paymentMethodTypeId in consumerTrustNegotiationInfo.PaymentMethodTypes) { dataModel.CreateConsumerTrustNegotiationOfferPaymentMethod( blotterId, newNegotiationId, Guid.NewGuid(), paymentMethodTypeId); } //Since we cannot create new counter payments, we will update the existing ones. foreach (ConsumerTrustNegotiationPaymentMethodTypeInfo consumerTrustNegotiationPaymentMethodTypeInfo in counterItems) { dataModel.UpdateConsumerTrustNegotiationCounterPaymentMethod( blotterId, null, new Object[] { consumerTrustNegotiationPaymentMethodTypeInfo.ConsumerTrustNegotiationOfferPaymentMethodId }, newNegotiationId, consumerTrustNegotiationPaymentMethodTypeInfo.PaymentMethodInfoId, consumerTrustNegotiationPaymentMethodTypeInfo.RowVersion); } } }
/// <summary> /// Determine whether there is a settlement (indirectly) associated with a match. /// </summary> /// <param name="transaction">The current transaction.</param> /// <param name="match">The match to check.</param> /// <param name="checkContra">If true, the contra match will also be checked.</param> /// <returns>True if the account is settled.</returns> public static bool IsSettled(DataModelTransaction transaction, MatchRow match, System.Boolean checkContra) { ConsumerDebtNegotiationRow[] consumerDebtNegotiationRows; ConsumerTrustNegotiationRow[] consumerTrustNegotiationRows; MatchRow contraMatch; if ((match as System.Data.DataRow).RowState == System.Data.DataRowState.Added) { return(false); } match.AcquireReaderLock(transaction); if (match.RowState == System.Data.DataRowState.Deleted || match.RowState == System.Data.DataRowState.Detached) { match.ReleaseReaderLock(transaction.TransactionId); return(false); } consumerDebtNegotiationRows = match.GetConsumerDebtNegotiationRows(); consumerTrustNegotiationRows = match.GetConsumerTrustNegotiationRows(); contraMatch = DataModel.Match.MatchKey.Find(match.ContraMatchId); match.ReleaseReaderLock(transaction.TransactionId); if (contraMatch == null) { return(false); } foreach (ConsumerDebtNegotiationRow row in consumerDebtNegotiationRows) { row.AcquireReaderLock(transaction); if (row.RowState == System.Data.DataRowState.Deleted || row.RowState == System.Data.DataRowState.Detached) { row.ReleaseReaderLock(transaction.TransactionId); continue; } int settlements = row.GetConsumerDebtSettlementRows().Length; row.ReleaseReaderLock(transaction.TransactionId); if (settlements != 0) { return(true); } } foreach (ConsumerTrustNegotiationRow row in consumerTrustNegotiationRows) { row.AcquireReaderLock(transaction); if (row.RowState == System.Data.DataRowState.Deleted || row.RowState == System.Data.DataRowState.Detached) { row.ReleaseReaderLock(transaction.TransactionId); continue; } int settlements = row.GetConsumerTrustSettlementRows().Length; row.ReleaseReaderLock(transaction.TransactionId); if (settlements != 0) { return(true); } } if (checkContra) { return(IsSettled(transaction, contraMatch, false)); } else { return(false); } }
/// <summary> /// Creates a new SettlementInfo instance. /// </summary> public SettlementItem(Guid matchId) { // Initialize the object. this.ChatItemList = new List <ChatItem>(); this.CounterPaymentMethods = new List <Guid>(); this.OfferPaymentMethods = new List <Guid>(); // The data model must be locked in order to navigate the data. The main idea here is to find the chat items and the negotiation elements // associated with this match and send that data to the foreground where it can be displayed. lock (DataModel.SyncRoot) { // This dialog will only handle matches that have this identifier. MatchRow matchRow = DataModel.Match.MatchKey.Find(matchId); if (matchRow == null) { this.Status = Status.Deleted; } else { // The status code drives the state of many of the controls in the console. this.Status = matchRow.StatusRow.StatusCode; // The negotiation table has a historical component. Ever time a change is made to the negotiation on either side a completely new record // is created to record the change. While the earlier versions are useful for a historical context and for reports, this console is only // interested in the current version of the negotiations. Int64 maxVersion = Int64.MinValue; ConsumerTrustNegotiationRow consumerTrustNegotiationRow = null; foreach (ConsumerTrustNegotiationRow versionRow in matchRow.GetConsumerTrustNegotiationRows()) { if (versionRow.Version > maxVersion) { maxVersion = versionRow.Version; consumerTrustNegotiationRow = versionRow; } } // Extract the scalar items used in the negotiation of a settlement. this.NegotiationItem = new NegotiationItem(consumerTrustNegotiationRow); // The Offer Payment Methods are a vector and need to be copied iteratively. foreach (ConsumerTrustNegotiationOfferPaymentMethodRow paymentMethod in consumerTrustNegotiationRow.GetConsumerTrustNegotiationOfferPaymentMethodRows()) { this.OfferPaymentMethods.Add(paymentMethod.PaymentMethodTypeId); } // The Conter Offer Payment methods are also a vector. foreach (ConsumerTrustNegotiationCounterPaymentMethodRow paymentMethod in consumerTrustNegotiationRow.GetConsumerTrustNegotiationCounterPaymentMethodRows()) { this.CounterPaymentMethods.Add(paymentMethod.PaymentMethodTypeId); } // When the console is initialized it is populated with every dialog item that has occurred for this match. foreach (ChatRow chatRow in matchRow.GetChatRows()) { ChatItem chatItem = new ChatItem(); chatItem.CreatedTime = chatRow.CreatedTime; chatItem.IsReply = chatRow.IsReply; chatItem.MatchId = matchId; chatItem.Message = chatRow.Message; this.ChatItemList.Add(chatItem); } } } }