예제 #1
0
 public override void SetOnlineRecord( IOnlineRecord otrans )
 {
     if( otrans == null )
     {
         this.onlineRecord = null;
         this.OnlineRecordId = null;
         return;
     }
     if( !(otrans is XmlOnlineRecord) )
         throw new ArgumentOutOfRangeException( "Must be an XmlOnlineRecord" );
     this.OnlineRecordId = otrans.Id;
     this.onlineRecord = (XmlOnlineRecord)otrans;
 }
예제 #2
0
        protected void Page_Load( object sender, EventArgs e )
        {
            this.otrans = WebGlobals.Provider.GetOnlineRecord( this.Id );
            this.account = WebGlobals.Provider.GetAccountBySource( this.otrans.OnlineSource );

            this.lblDate.Text = string.Format( "{0:M/d/yyyy}", this.otrans.Date );
            this.lblAmount.Text = string.Format( "{0:N2}", this.otrans.Amount );
            this.lblDescription.Text = this.otrans.Description;

            DataHelper helper = new DataHelper( WebGlobals.Provider );
            this.bestItem = helper.GetBestMatchingLineItem( this.account, this.otrans );

            this.items.AddRange( WebGlobals.Provider.GetPotentialMatchingLineItems( this.account, this.otrans ) );

            this.records = WebGlobals.Provider.GetRecordsByDescription( this.otrans.OnlineSource, this.otrans.Description );
        }
예제 #3
0
 public void SaveRecord( IOnlineRecord record )
 {
     SaveDataObject( (SqlRecord)record );
 }
예제 #4
0
        public IOnlineRecord FindRecord( IOnlineRecord record )
        {
            // CREATE UNIQUE NONCLUSTERED INDEX [IDX_Unique] ON [dbo].[Record]
            // ( [Source], [Description], [PostDate], [Amount], [BankId], [Number] );

            SqlRecord foundRecord = new SqlRecord();
            foundRecord.OnlineSource = record.OnlineSource;
            foundRecord.BankId = record.BankId;
            foundRecord.Date = record.Date;
            foundRecord.Description = record.Description;
            foundRecord.Amount = record.Amount;
            foundRecord.Number = record.Number;
            foundRecord.SetParameters( command.Parameters );
            foundRecord.FillParameters( command.Parameters );

            SqlDataReader data = ExecuteReader( "SELECT ID, LineItemID FROM [{0}] WHERE [Source]=@source AND [Description]=@description AND [PostDate]=@date AND [Amount]=@amount AND [BankId]=@bankId AND [Number]=@number",
                recordTemplate.TableName );
            try
            {
                if( data.Read() )
                {
                    foundRecord.SetSaved( true );
                    foundRecord.Id = Convert.ToInt32( data["ID"] );
                    foundRecord.LineItemId = Convert.ToInt32( data["LineItemID"] );
                }
                else
                {
                    // No record found in the database.
                    foundRecord = null;
                }
            }
            finally
            {
                data.Close();
            }

            return foundRecord;
        }
예제 #5
0
 public void SaveMatchingRecord( ILineItem item, IOnlineRecord record )
 {
     ExecuteNonQuery( "UPDATE [{0}] SET LineItemId={2} WHERE Id={1}",
         recordTemplate.TableName, record.Id, item.Id );
 }
예제 #6
0
        /// <summary>
        /// Gets a line item that matches the information in the given record.
        /// </summary>
        /// <param name="account">IAccount in which to search.</param>
        /// <param name="record">IOnlineRecord with properties to match.</param>
        /// <returns>
        /// ILineItem object of best match or null if no match found.
        /// </returns>
        public IEnumerable<ILineItem> GetPotentialMatchingLineItem( IAccount account, IOnlineRecord record )
        {
            bool ignorePriorMatches = false;

            // Determine whether to search for a credit or debit transaction.
            string searchType;
            if( account.AccountType == AccountType.Asset ||
                account.AccountType == AccountType.Checking ||
                account.AccountType == AccountType.Savings )
            {
                if( record.Amount < 0 ) searchType = "C";
                else searchType = "D";
            }
            else if( account.AccountType == AccountType.Liability ||
                account.AccountType == AccountType.CreditCard )
            {
                if( record.Amount < 0 ) searchType = "C";
                else searchType = "D";
            }
            else
                throw new ApplicationException( "Unsupported account type." );

            // Enforce a minimum starting date.
            // This is because SQL Server chokes on dates prior to 1753,
            // so we can't just pass it DateTime.MinValue.
            DateTime minDate = new DateTime( 1753, 1, 1 );
            if( account.StartingDate != null )
                minDate = account.StartingDate.Value;

            // Get a short list of possible candidates.
            // 1. Match must be for same account
            // 2. Match must be on or before the post date
            // 3. Match must be same amount and transaction type

            List<ILineItem> matches = new List<ILineItem>();
            using( MySqlCommand command = lineItemsContext.CreateCommand() )
            {
                string extraClause = string.Empty;
                if( ignorePriorMatches ) extraClause = "r.ID IS NULL AND";

                command.CommandText = string.Format(
                    "SELECT * FROM LineItem l" +
                    " INNER JOIN Account a ON (a.ID = l.AccountID)" +
                    " LEFT OUTER JOIN OnlineRecord r ON (r.LineItemID = l.ID)" +
                    " WHERE {0} a.OnlineSource=@source" +
                    " AND l.IsVoided=0" +
                    " AND l.TransactionType=@type" +
                    " AND l.TransactionDate>=@mindate" +
                    " AND l.TransactionDate<=@date" +
                    " AND l.Amount=@amount" +
                    " ORDER BY l.TransactionDate DESC", extraClause );

                command.Parameters.AddWithValue( "@source", record.OnlineSource );
                command.Parameters.Add( "@date", MySqlDbType.Date ).Value = record.Date.Date;
                command.Parameters.Add( "@mindate", MySqlDbType.Date ).Value = minDate.Date;
                command.Parameters.AddWithValue( "@type", searchType );
                command.Parameters.AddWithValue( "@amount", Math.Abs( record.Amount ) );

                DataTable table = this.lineItemsContext.GetRows( command );
                foreach( DataRow row in table.Rows )
                {
                    MySqlLineItem match = new MySqlLineItem( row );
                    matches.Add( match );
                }
            }

            return matches;
        }
예제 #7
0
        public ILineItem FindMatchingLineItem( IAccount account, IOnlineRecord record, bool allowDuplicates )
        {
            // Determine whether to search for a credit or debit transaction.
            string searchType;
            if( account.AccountType == AccountType.Asset ||
                account.AccountType == AccountType.Checking ||
                account.AccountType == AccountType.Savings )
            {
                if( record.Amount < 0 ) searchType = "C";
                else searchType = "D";
            }
            else if( account.AccountType == AccountType.Liability ||
                account.AccountType == AccountType.CreditCard )
            {
                if( record.Amount < 0 ) searchType = "D";
                else searchType = "C";
            }
            else
                throw new ApplicationException( "Unsupported account type." );

            // Enforce a minimum starting date of 1800.
            // This is because SQL Server chokes on dates prior to 1753,
            // so we can't just pass it DateTime.MinValue.
            DateTime minDate = new DateTime( 1800, 1, 1 );
            if( account.StartingDate != null )
                minDate = account.StartingDate.Value;

            // Get a short list of possible candidates.
            // 1. Match must be for same account
            // 2. Match must be on or before the post date
            // 3. Match must be same amount and transaction type

            command.Parameters.Clear();
            command.Parameters.Add( "@source", SqlDbType.VarChar, 100 ).Value = record.OnlineSource;
            command.Parameters.Add( "@date", SqlDbType.DateTime ).Value = record.Date;
            command.Parameters.Add( "@mindate", SqlDbType.DateTime ).Value = minDate;
            command.Parameters.Add( "@type", SqlDbType.Char, 1 ).Value = searchType;
            command.Parameters.Add( "@amount", SqlDbType.Decimal, 2 ).Value = Math.Abs( record.Amount );

            string dupeClause = string.Empty;
            if( !allowDuplicates ) dupeClause = "r.ID IS NULL AND";

            List<ILineItem> matches = new List<ILineItem>();
            SqlDataReader data = ExecuteReader(
                "SELECT * FROM [LineItem] l" +
                " INNER JOIN [Account] a ON (a.ID = l.AccountID)" +
                " LEFT JOIN [Record] r ON (r.LineItemID = l.ID)" +
                " WHERE {0} a.Source=@source" +
                " AND l.TransactionDate>=@mindate" +
                " AND l.TransactionDate<=@date" +
                " AND l.TransactionType=@type" +
                " AND l.Amount=@amount" +
                " ORDER BY l.[TransactionDate] DESC", dupeClause );
            try
            {
                while( data.Read() )
                {
                    SqlLineItem match = new SqlLineItem();
                    match.SetSaved( true );
                    match.Fill( data );
                    matches.Add( match as ILineItem );
                }
            }
            finally
            {
                data.Close();
            }

            // Further tests.

            if( matches.Count == 0 )
            {
                // No results, can just exit.
                return null;
            }
            else if( matches.Count == 1 )
            {
                // The above filter will get most cases
                return matches[0];
            }

            // Returned more than one possible match.
            // Narrow it down to one.

            SqlLineItem minLineItem = null;
            int minDays = Int32.MaxValue;

            foreach( SqlLineItem item in matches )
            {
                // If one of the matches has the same number, return it.
                if( record.Number.Equals( item.Number, StringComparison.Ordinal ) )
                    return item;

                // Otherwise pick the match closest to the record date.
                TimeSpan span = record.Date - item.TransactionDate;
                if( span.Days < minDays )
                {
                    minDays = span.Days;
                    minLineItem = item;
                }
            }

            return minLineItem;
        }
 public bool Contains( IOnlineRecord record )
 {
     foreach( IOnlineRecord otrans in this.Transactions )
     {
         if( otrans == record ) return true;
     }
     return false;
 }
예제 #9
0
 public void SetOnlineRecord( IOnlineRecord otrans )
 {
     if( otrans != null )
         this.OnlineRecordId = otrans.Id;
     else
         this.OnlineRecordId = null;
 }
예제 #10
0
 public IOnlineRecord FindRecord( IOnlineRecord record )
 {
     throw new NotImplementedException();
 }
예제 #11
0
 public ILineItem GetMatchingLineItem( IAccount account, IOnlineRecord record )
 {
     throw new NotImplementedException();
 }
예제 #12
0
 public void SaveRecord( IOnlineRecord record )
 {
     Assert.PreCondition( ledger != null );
     saveOnClose = true;
 }
예제 #13
0
 public void CreateMissingRecord( IOnlineRecord record )
 {
     throw new NotImplementedException();
 }
예제 #14
0
 public void SaveMatchingRecord( ILineItem item, IOnlineRecord record )
 {
     throw new NotImplementedException();
 }
 /// <summary>
 /// Add a transaction to the list and generate an appropriate sequence
 /// number for it.  If the transaction takes place on the same day as
 /// a previous transaction, a new sequence number is created. 
 /// Otherwise, the default sequence number of 0 is applied.
 /// </summary>
 /// <param name="otrans"></param>
 public void AddWithSequence( IOnlineRecord otrans )
 {
     int sequence = 0;
     // Sequence numbers are generated for transactions that occur on
     // the same date to distinguish them.  This assumes that the bank
     // is always going to list their transactions in the same order.
     string key = otrans.Date.ToString( "yyyyMMdd" );
     if( this.keyCounts.ContainsKey( key ) )
         sequence = this.keyCounts[key]++;
     else
         this.keyCounts.Add( key, 1 );
     otrans.Sequence = sequence;
     this.Transactions.Add( otrans );
 }
 public IOnlineRecord FindDuplicateByDescription( IOnlineRecord record )
 {
     foreach( IOnlineRecord otrans in this.Transactions )
     {
         if( otrans.Date == record.Date &&
             otrans.Description == record.Description &&
             otrans.Amount == record.Amount ) return otrans;
     }
     return null;
 }
예제 #17
0
 public ILineItemData FindMatchingLineItem( IOnlineRecord record )
 {
     return storage.FindMatchingLineItem( record );
 }
예제 #18
0
 /// <summary>
 /// Gets an online record from the data store matching the criteria
 /// in the given record.
 /// </summary>
 /// <param name="record">Online record with properties to match.</param>
 /// <returns>IOnlineRecord object or null if not found.</returns>
 public IOnlineRecord GetMatchingRecord( IOnlineRecord record )
 {
     List<XmlOnlineRecord> potentialMatches = new List<XmlOnlineRecord>();
     foreach( XmlOnlineRecord otrans in this.records.Values )
     {
         if( otrans.OnlineSource == record.OnlineSource
             && otrans.Date == record.Date
             && otrans.Amount == record.Amount
             && otrans.Sequence == record.Sequence )
             potentialMatches.Add( otrans );
     }
     if( potentialMatches.Count > 1 )
         throw new DataProviderException(
             "There are more than one potential online record matches for " + record.ToString() );
     if( potentialMatches.Count == 1 )
         return potentialMatches[0];
     else
         return null;
 }
예제 #19
0
        /// <summary>
        /// Get the single best matching line item for the given record.
        /// </summary>
        /// <param name="account"></param>
        /// <param name="record"></param>
        /// <returns>An ILineItem or null if there were no matches.</returns>
        public ILineItem GetBestMatchingLineItem( IAccount account, IOnlineRecord record )
        {
            List<ILineItem> matches = new List<ILineItem>(
                this.Provider.GetPotentialMatchingLineItems( account, record ) );

            foreach( ILineItem item in new List<ILineItem>( matches ) )
            {
                // If we have a Number, use it as the primary matching factor.
                if( record.Number != null )
                {
                    // If the line item doesn't have a number, it can never match.
                    if( item.Number == null )
                        matches.Remove( item );

                    // If the numbers don't match, remove it.
                    if( item.Number != record.Number )
                        matches.Remove( item );
                }

                if( record.Number == null && item.Number == null )
                {
                    // As a precaution, cannot match non-checks
                    // to a transaction more than 30 days older.
                    TimeSpan span = record.Date - item.TransactionDate;
                    if( span.TotalDays > 30 ) matches.Remove( item );
                }
            }

            if( matches.Count == 0 )
                return null;
            else if( matches.Count == 1 )
                return matches[0];

            // There are still multiple potential matches.
            // At this point we need to try some more clever schemes.

            int maxRelevance = 0;
            ILineItem maxRelevanceItem = null;

            // Attempt to match a line item with a similar description.
            foreach( ILineItem item in new List<ILineItem>( matches ) )
            {
                ITransaction trans = this.Provider.GetTransaction( item.TransactionId );
                int relevance = this.CompareDescriptions( trans.Payee, record.Description );
                if( relevance >= maxRelevance )
                {
                    maxRelevance = relevance;
                    maxRelevanceItem = item;
                }
            }

            if( maxRelevanceItem != null )
                return maxRelevanceItem;

            // If there are STILL multiple potential matches,
            // Pick the one with a date nearest to the online record.

            ILineItem minItem = null;
            int minDays = Int32.MaxValue;
            foreach( ILineItem item in matches )
            {
                TimeSpan span = record.Date - item.TransactionDate;
                if( span.Days <= minDays )
                {
                    minDays = span.Days;
                    minItem = item;
                }
            }

            return minItem;
        }
예제 #20
0
 public IEnumerable<ILineItem> GetPotentialMatchingLineItems( IAccount account, IOnlineRecord record )
 {
     throw new NotImplementedException();
 }
예제 #21
0
 public IOnlineRecord GetMatchingRecord( IOnlineRecord record )
 {
     throw new NotImplementedException();
 }
예제 #22
0
 public ILineItemData FindMatchingLineItem( IOnlineRecord record )
 {
     Assert.PreCondition( ledger != null );
     return ledger.SearchForMatchingLineItem( (OnlineTransaction)record, true );
 }
예제 #23
0
        /// <summary>
        /// Get all potential matching line items for the given record.
        /// It is assumed the caller will prune down the list even more.
        /// </summary>
        /// <param name="account"></param>
        /// <param name="record"></param>
        /// <returns></returns>
        public IEnumerable<ILineItem> GetPotentialMatchingLineItems( IAccount account, IOnlineRecord record )
        {
            TransactionType searchType;
            if( account.IsAssetAccount() )
            {
                if( record.Amount < 0 ) searchType = TransactionType.Credit;
                else searchType = TransactionType.Debit;
            }
            else if( account.IsLiabilityAccount() )
            {
                if( record.Amount < 0 ) searchType = TransactionType.Debit;
                else searchType = TransactionType.Credit;
            }
            else
                throw new DataProviderException( "Unsupported account type " + account.AccountType.ToString() );

            List<ILineItem> matches = new List<ILineItem>();
            foreach( XmlLineItem item in this.lineItems.Values )
            {
                // Ensure is from the right account
                if( item.AccountId != account.Id ) continue;

                // Don't match unless it is the right type of transaction.
                if( item.TransactionType != searchType ) continue;

                // Never match a reconciled line item
                if( item.IsReconciled ) continue;

                // Never match a voided line item
                if( item.IsVoided ) continue;

                // Don't match a line item which was already matched to something
                if( item.OnlineRecordId != null ) continue;

                // If amounts are the same, it could be a match.
                if( item.Amount != Math.Abs( record.Amount ) ) continue;

                // But...
                // Online transaction must be later than or same date as transaction
                if( record.Date < item.TransactionDate ) continue;

                // If passed tests above, add to list of matches.
                matches.Add( item );
            }

            return matches;
        }
예제 #24
0
 public ILineItem FindMatchingLineItem( IAccount account, IOnlineRecord record )
 {
     return FindMatchingLineItem( account, record, false );
 }
예제 #25
0
 public virtual void SetOnlineRecord( IOnlineRecord otrans )
 {
     if( otrans == null )
         throw new ArgumentNullException( "An online record object must be provided" );
     this.OnlineRecordId = otrans.Id;
 }
예제 #26
0
 /// <summary>
 /// Gets an online record from the data store matching the criteria
 /// in the given record.
 /// </summary>
 /// <param name="record">Online record with properties to match.</param>
 /// <returns>
 /// IOnlineRecord object or null if not found.
 /// </returns>
 public IOnlineRecord GetMatchingRecord( IOnlineRecord record )
 {
     // Uses the source/date/sequence/amount to identify the record.
     using( MySqlCommand command = onlineRecordsContext.CreateCommand() )
     {
         command.CommandText = SQL.SelectMatchingRecord;
         command.Parameters.AddWithValue( "@source", record.OnlineSource );
         command.Parameters.AddWithValue( "@date", record.Date );
         command.Parameters.AddWithValue( "@amount", record.Amount );
         command.Parameters.AddWithValue( "@sequence", record.Sequence );
         DataTable table = this.onlineRecordsContext.GetRows( command );
         if( table.Rows.Count == 1 )
         {
             // If only one row found, return it.
             return new MySqlOnlineRecord( table.Rows[0] );
         }
     }
     // Otherwise if row was not found, return null.
     return null;
 }