/// <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();
            }
        }
Пример #3
0
        /// <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);
        }
Пример #4
0
        /// <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);
                }
            }
        }
Пример #5
0
        /// <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);
            }
        }
Пример #6
0
        /// <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);
                    }
                }
            }
        }