Пример #1
0
        public Ledger LoadLedger()
        {
            Ledger ledger = new Ledger();

            //ledger.CreateAccount( AccountType.Income, "Income" );
            //ledger.CreateAccount( AccountType.Expense, "Expenses" );

            StreamReader reader = File.OpenText( filename );
            try
            {
                Account account;
                string headerLine;

                headerLine = reader.ReadLine();
                if( !headerLine.StartsWith( "!Type" ) )
                    throw new StorageException( "The file " + filename + " does not start with a !Type declaration." );

                string accountName = Path.GetFileNameWithoutExtension( filename );

                switch( headerLine.Trim() )
                {
                    case "!Type:Bank":
                    case "!Type:Cash":
                        account = ledger.CreateAccount( AccountType.Asset, accountName );
                        break;
                    case "!Type:CCard":
                        account = ledger.CreateAccount( AccountType.Liability, accountName );
                        break;
                    case "!Type:Invst":
                    case "!Type:Oth A":
                        account = ledger.CreateAccount( AccountType.Asset, accountName );
                        break;
                    case "!Type:Oth L":
                        account = ledger.CreateAccount( AccountType.Liability, accountName );
                        break;
                    case "!Account":
                    case "!Type:Cat":
                    case "!Type:Class":
                    case "!Type:Memorized":
                        throw new StorageException( "Header not supported: " + headerLine );
                    default:
                        throw new StorageException( "Header not recognized: " + headerLine );
                }

                int lineNumber = 1;
                while( !reader.EndOfStream )
                {
                    lineNumber = LoadTransaction( reader, lineNumber, ledger, account );
                }
            }
            finally
            {
                reader.Close();
            }

            return ledger;
        }
Пример #2
0
 public void Close()
 {
     if( saveOnClose )
     {
         dataMgr.Lock();
         dataMgr.Save( ledger );
         dataMgr.Unlock();
     }
     ledger = null;
 }
Пример #3
0
    protected void Page_Load( object sender, EventArgs e )
    {
        this.PreRender += new EventHandler( Page_PreRender );

        dataMgr = new DataManager();
        ledger = dataMgr.Load();

        if( !IsPostBack )
        {
            txtDate.Text = DateTime.Now.ToShortDateString();
        }
    }
Пример #4
0
        private void AssignLineItemIdentifiers( Ledger ledger )
        {
            // Determine the next available Id by finding the maximum used Id
            foreach( LineItem item in ledger.LineItems )
            {
                if( item.Id >= ledger.NextLineItemId )
                    ledger.NextLineItemId = item.Id + 1;
            }

            // Assign an Id to any line item that doesn't have one
            foreach( LineItem item in ledger.LineItems )
            {
                if( item.Id == 0 )
                    item.Id = ledger.GetNextLineItemId();
                // Ensure online transaction matches the line item ID
                if( item.OnlineTransaction != null )
                    item.OnlineTransaction.MatchingId = item.Id;
            }
        }
Пример #5
0
 public void ValidateIdentifierLinks( Ledger ledger )
 {
     foreach( LineItem item in ledger.LineItems )
     {
         if( item.Id < 1 )
             throw new LineItemException( string.Format(
                 "LineItem ({0}) in Transaction #{1} does not have a valid identifier.",
                 item.ToString(), item.Transaction.Id ) );
         if( item.OnlineTransaction != null && item.OnlineTransaction.MatchingId != item.Id )
             throw new LineItemException( string.Format(
                 "OnlineTransaction.MatchingId ({0}) != LineItem.Id ({1}) in Transaction #{2}",
                 item.OnlineTransaction.MatchingId, item.Id, item.Transaction.Id ) );
     }
 }
Пример #6
0
        public void SaveLedger( Ledger ledger )
        {
            ledger.ValidateReferentialIntegrity();

            // Ensure that identifiers match appropriately before saving.
            ValidateIdentifierLinks( ledger );

            /*
            // TODO: Remove this after testing
            ledger.RecurringTransactions.Clear();
            RecurringTransaction r = new RecurringTransaction();
            r.StartingDate = new DateTime( 2007, 10, 15 );
            r.EndingDate = DateTime.MaxValue;
            r.Date = new DateTime( 2007, 10, 15 );
            r.Amount = 129.69M;
            r.Description = "Ameriprise Financial";
            r.Memo = "Life insurance payment";
            r.LineItems.Add( new LineItemCredit( ledger.GetAccount( "Suntrust" ), r.Amount ) );
            r.LineItems.Add( new LineItemDebit( ledger.GetAccount( "Expenses" ), r.Amount ) );
            r.AddMonths = 1;
            ledger.RecurringTransactions.Add( r );
            */

            StreamWriter writer = File.CreateText( filename );
            try
            {
                XmlTextWriter xmlWriter = new XmlTextWriter( writer );
                try
                {
                    xmlWriter.WriteStartDocument();
                    xmlWriter.WriteWhitespace( Environment.NewLine );
                    xmlWriter.WriteStartElement( "Ledger" );
                    xmlWriter.WriteAttributeString( "xmlns", "http://www.uvteksoftware.com/xsd/UvMoneyLedger.xsd" );
                    xmlWriter.WriteAttributeString( "Version", "1.2" );
                    xmlWriter.WriteWhitespace( Environment.NewLine );

                    xmlWriter.WriteElementString( "MergeDate", ledger.MergeDate.ToString() );
                    xmlWriter.WriteWhitespace( Environment.NewLine );

                    // Write accounts
                    xmlWriter.WriteWhitespace( Environment.NewLine );
                    xmlWriter.WriteStartElement( "Accounts" );
                    xmlWriter.WriteWhitespace( Environment.NewLine );
                    foreach( Account account in ledger.Accounts )
                    {
                        SerializeAccount( xmlWriter, account );
                        xmlWriter.WriteWhitespace( Environment.NewLine );
                    }
                    xmlWriter.WriteEndElement(); // </Accounts>
                    xmlWriter.WriteWhitespace( Environment.NewLine );

                    // Write recurring transactions
                    if( ledger.RecurringTransactions.Count > 0 )
                    {
                        xmlWriter.WriteWhitespace( Environment.NewLine );
                        xmlWriter.WriteStartElement( "RecurringTransactions" );
                        xmlWriter.WriteWhitespace( Environment.NewLine );
                        foreach( RecurringTransaction retrans in ledger.RecurringTransactions )
                        {
                            SerializeRecurringTransaction( xmlWriter, retrans );
                            xmlWriter.WriteWhitespace( Environment.NewLine );
                        }
                        xmlWriter.WriteEndElement(); // </RecurringTransactions>
                        xmlWriter.WriteWhitespace( Environment.NewLine );
                    }

                    // Write transactions
                    xmlWriter.WriteWhitespace( Environment.NewLine );
                    xmlWriter.WriteStartElement( "Transactions" );
                    xmlWriter.WriteWhitespace( Environment.NewLine );
                    foreach( Transaction trans in ledger.Transactions )
                    {
                        // Do not serialize auto-generated transactions
                        if( !( trans is MissingTransaction ) )
                        {
                            trans.Validate();
                            SerializeTransaction( xmlWriter, trans );
                            xmlWriter.WriteWhitespace( Environment.NewLine );
                        }
                    }

                    xmlWriter.WriteWhitespace( Environment.NewLine );
                    xmlWriter.WriteComment( "Insert new transactions here." );
                    xmlWriter.WriteWhitespace( Environment.NewLine );

                    // Write out a transaction template
                    StringBuilder sb = new StringBuilder();
                    sb.Append( Environment.NewLine );
                    sb.Append( "<Transaction>" );
                    sb.Append( Environment.NewLine );
                    sb.Append( "<Date></Date>" );
                    sb.Append( Environment.NewLine );
                    sb.Append( "<Number></Number>" );
                    sb.Append( Environment.NewLine );
                    sb.Append( "<Amount>0.00</Amount>" );
                    sb.Append( Environment.NewLine );
                    sb.Append( "<Description>Payee</Description>" );
                    sb.Append( Environment.NewLine );
                    sb.Append( "<Memo></Memo>" );
                    sb.Append( Environment.NewLine );
                    sb.Append( "<LineItems>" );
                    sb.Append( Environment.NewLine );
                    sb.Append( "<Credit Account=\"\" />" );
                    sb.Append( Environment.NewLine );
                    sb.Append( "<Debit Account=\"Expenses\" Category=\"\" />" );
                    sb.Append( Environment.NewLine );
                    sb.Append( "</LineItems>" );
                    sb.Append( Environment.NewLine );
                    sb.Append( "</Transaction>" );
                    sb.Append( Environment.NewLine );
                    xmlWriter.WriteComment( sb.ToString() );
                    xmlWriter.WriteWhitespace( Environment.NewLine );
                    xmlWriter.WriteWhitespace( Environment.NewLine );

                    xmlWriter.WriteEndElement(); // </Transactions>
                    xmlWriter.WriteWhitespace( Environment.NewLine );

                    OnlineTransactionStorageXml ostorage = new OnlineTransactionStorageXml();

                    // Write online transactions that are missing from the ledger
                    if( ledger.MissingTransactions.Count > 0 )
                    {
                        xmlWriter.WriteStartElement( "MissingOnlineTransactions" );
                        xmlWriter.WriteWhitespace( Environment.NewLine );
                        foreach( OnlineTransaction otrans in ledger.MissingTransactions )
                        {
                            ostorage.SerializeTransaction( xmlWriter, otrans );
                            xmlWriter.WriteWhitespace( Environment.NewLine );
                        }
                        xmlWriter.WriteEndElement(); // </MissingOnlineTransactions>
                        xmlWriter.WriteWhitespace( Environment.NewLine );
                    }

                    xmlWriter.WriteEndElement(); // </Ledger>
                    xmlWriter.WriteWhitespace( Environment.NewLine );
                    xmlWriter.WriteEndDocument();
                }
                finally
                {
                    xmlWriter.Close();
                }
            }
            finally
            {
                writer.Close();
            }
        }
Пример #7
0
        public Ledger LoadLedger()
        {
            Ledger ledger = new Ledger();

            FileInfo fi = new FileInfo( filename );
            if( !fi.Exists ) return ledger;

            OnlineTransactionStorageXml ostorage = new OnlineTransactionStorageXml();

            Version version = new Version( 1, 0 );

            // I'm not precisely sure why I chose to parse the XML manually
            // instead of using XmlSerializer.
            // I guess I just didn't want to markup my classes with Xml tags.

            StreamReader reader = File.OpenText( filename );
            try
            {
                XmlTextReader xmlReader = new XmlTextReader( reader );
                try
                {
                    while( xmlReader.Read() )
                    {
                        switch( xmlReader.NodeType )
                        {
                            case XmlNodeType.Element:
                                switch( xmlReader.Name )
                                {
                                    case "MergeDate":
                                        ledger.MergeDate = DateTime.Parse( xmlReader.ReadString() );
                                        break;
                                    case "Ledger":
                                        version = new Version( xmlReader.GetAttribute( "Version" ) );
                                        break;
                                    case "Accounts":
                                        break;
                                    case "Account":
                                        Account account = DeserializeAccount( xmlReader, version );
                                        account.Ledger = ledger;
                                        // Verify that we are not adding any duplicate Ids or Names
                                        Account testAccount = ledger.FindAccount( account.Id );
                                        if( testAccount != null )
                                            throw new ApplicationException( "There is already an account with Id " + account.Id );
                                        testAccount = ledger.FindAccount( account.Name );
                                        if( testAccount != null )
                                            throw new ApplicationException( "There is already an account named " + account.Name );
                                        // Passed validation, add the account
                                        ledger.Accounts.Add( account );
                                        break;
                                    case "Transactions":
                                        break;
                                    case "Transaction":
                                        Transaction trans = DeserializeTransaction( xmlReader, version, ledger );
                                        // Migrate from deprecated versions
                                        trans.Migrate( version );
                                        // Validate the transaction
                                        trans.Validate();
                                        if( trans.Id != 0 )
                                        {
                                            // Ensure no other transactions with the same Id are in the ledger
                                            Transaction testTrans = ledger.GetTransaction( trans.Id );
                                            if( testTrans != null )
                                                throw new ApplicationException( "There is already a transaction with Id " + trans.Id );
                                        }
                                        ledger.AddTransaction( trans );
                                        break;
                                    case "MissingOnlineTransactions":
                                        break;
                                    case "OnlineTransaction":
                                        OnlineTransaction otrans = ostorage.DeserializeTransaction( xmlReader, version );
                                        ledger.MissingTransactions.Add( otrans );
                                        break;
                                    case "RecurringTransactions":
                                        break;
                                    case "RecurringTransaction":
                                        RecurringTransaction rtrans = DeserializeRecurringTransaction( xmlReader, version, ledger );
                                        ledger.RecurringTransactions.Add( rtrans );
                                        break;
                                }
                                break;
                        }
                    }
                }
                finally
                {
                    xmlReader.Close();
                }
            }
            finally
            {
                reader.Close();
            }

            AssignTransactionIdentifiers( ledger );
            AssignLineItemIdentifiers( ledger );
            ValidateIdentifierLinks( ledger );

            ledger.ValidateReferentialIntegrity();

            // Set load date so we can determine if data in memory is stale
            ledger.LoadDate = DateTime.Now;

            return ledger;
        }
Пример #8
0
 /// <summary>
 /// 
 /// </summary>
 /// <param name="ledger"></param>
 /// <exception cref="FileLockerException">If the file is locked by someone else.</exception>
 public void Save( Ledger ledger )
 {
     stopwatch.Reset();
     stopwatch.Start();
     this.ledger = ledger;
     ledgerStorage.SaveLedger( ledger );
     cacheTime = ledgerStorage.FileDateTime;
     stopwatch.Stop();
     saveTimeMilliseconds = stopwatch.ElapsedMilliseconds;
 }
Пример #9
0
 static GlobalWebData()
 {
     manager = new DataManager();
     ledger = null;
 }
Пример #10
0
 public void Close()
 {
     if( saveOnClose )
         dataMgr.Save( ledger );
     ledger = null;
 }
Пример #11
0
        private Account ParseAccount( Ledger ledger, string text, decimal amount, out Category category )
        {
            Account account = null;
            category = null;
            string accountName = null, categoryName = null;

            if( text != null && text[0] == '[' )
            {
                // Transfer category
                accountName = text.Trim( '[', ']' );
                // We really have no way of knowing what kind of account we
                // are transferring to.
                // We now guess at an account type based on the name.
                // User will have to correct if it's wrong.
                AccountType type = AccountType.Asset;
                string searchText = accountName.ToLower();
                if( searchText.IndexOf( "savings" ) >= 0 )
                    type = AccountType.Asset;
                else if( searchText.IndexOf( "visa" ) >= 0
                    || searchText.IndexOf( "mastercard" ) >= 0
                    || searchText.IndexOf( "credit" ) >= 0 )
                    type = AccountType.Liability;
                else if( searchText.IndexOf( "loan" ) >= 0
                    || searchText.IndexOf( "mortgage" ) >= 0 )
                    type = AccountType.Liability;
                account = ledger.GetAccount( type, accountName );
            }
            else
            {
                // Income or expense category
                if( text != null )
                {
                    string[] catNames = text.Split( ':' );
                    if( catNames.Length > 0 )
                    {
                        // We only support 1 level of sub-category... more than that are just concatenated with a dot.
                        accountName = catNames[0].Trim();
                        if( catNames.Length > 1 )
                            categoryName = string.Join( ".", catNames, 1, catNames.Length - 1 ).Trim();
                    }
                }
                if( amount < 0 )
                {
                    if( text == null ) accountName = "Expenses";
                    account = ledger.GetAccount( AccountType.Expense, accountName );
                }
                else
                {
                    if( text == null ) accountName = "Income";
                    account = ledger.GetAccount( AccountType.Income, accountName );
                }
                if( categoryName != null )
                {
                    category = account.GetCategory( categoryName );
                }
            }

            return account;
        }
Пример #12
0
        private int LoadTransaction( StreamReader reader, int lineNumber, Ledger ledger, Account account )
        {
            Transaction trans = new Transaction();
            try
            {
                LineItem item;
                decimal transAmount = 0;
                string lineItemCategory = null;
                string lineItemMemo = null;
                decimal lineItemAmount = 0;
                string transCategory = null;
                bool isCleared = false;
                string checkNumber = null;
                string investmentName = null;
                string investmentPrice = null;
                string investmentShares = null;
                string investmentCommission = null;

                while( !reader.EndOfStream )
                {
                    string line = reader.ReadLine();
                    if( line[0] == '^' ) break;

                    switch( line[0] )
                    {
                        case 'D':
                            // Date
                            // MM/DD'YYYY
                            // We handle a bunch of different separators
                            // TODO: Handle DD/MM/YYYY European form ??
                            Regex regex = new Regex( "D(\\d+)['.-/]\\s*(\\d+)['.-/]\\s*(\\d+)" );
                            if( !regex.IsMatch( line ) )
                                throw new StorageException( "Cannot parse date " + line );
                            Match match = regex.Match( line );
                            int month = Int32.Parse( match.Groups[1].Value );
                            int day = Int32.Parse( match.Groups[2].Value );
                            int year = Int32.Parse( match.Groups[3].Value );
                            // Handle two-digit dates which are assumed to be 1900s
                            if( year < 100 ) year += 1900;
                            trans.Date = new DateTime( year, month, day );
                            break;
                        case 'T':
                            // Transaction amount (presumably dollars).
                            // This is actually the amount that involves this account,
                            // not necessarily the total transaction amount.
                            // Transactions with splits may have a different total amount.
                            // Particularly paychecks.
                            // The transaction total is the sum of the credits.

                            #region Split transaction sample
                            // Split sample, as exported from MS Money:
                            //
                            // D5/23'2006
                            // CX
                            // MPaycheck + cash out
                            // T384.04							<-- (debit) not the total!
                            // PEmmanuel Church
                            // LWages & Salary:Gross Pay-Cyn
                            // SWages & Salary:Gross Pay-Cyn
                            // EGross Salary
                            // $625.33							<-- (credit)
                            // STaxes:Federal Income
                            // EFederal
                            // $-15.45							<-- (debit)
                            // STaxes:Social Security
                            // EFica
                            // $-38.77							<-- (debit)
                            // STaxes:Medicare Tax 20
                            // EMedicare
                            // $-9.07							<-- (debit)
                            // STaxes:State Income Ta
                            // EState
                            // $-18.00							<-- (debit)
                            // SGroceries
                            // ECash for groceries
                            // $-150.00							<-- (debit)
                            // SCash Withdrawal
                            // ECash
                            // $-50.00							<-- (debit)
                            // SPiano Business
                            // EStudents?
                            // $40.00							<-- (credit)
                            // ^
                            #endregion

                            transAmount = Decimal.Parse( line.Substring( 1 ) );
                            trans.Amount = Math.Abs( transAmount );
                            break;
                        case 'C':
                            // Cleared status
                            // CX = cleared
                            if( line[1] == 'X' ) isCleared = true;
                            break;
                        case 'N':
                            // Check number
                            // If investment account, this is the action: buy, sell, etc.
                            checkNumber = line.Substring( 1 );
                            break;
                        case 'P':
                            // Payee
                            trans.Description = line.Substring( 1 );
                            break;
                        case 'M':
                            // Memo
                            trans.Memo = line.Substring( 1 );
                            break;
                        case 'L':
                            // Category/Account
                            transCategory = line.Substring( 1 );
                            break;
                        case 'S':
                            // Split line item category (like L above)
                            // (If there are splits, it changes the meaning of the T amount.)
                            lineItemCategory = line.Substring( 1 );
                            lineItemMemo = null;
                            lineItemAmount = 0;
                            break;
                        case 'E':
                            // Split line item memo (like M above)
                            lineItemMemo = line.Substring( 1 );
                            break;
                        case '$':
                            // Split line item amount (like T above)
                            lineItemAmount = Decimal.Parse( line.Substring( 1 ) );
                            item = BuildLineItem( ledger, trans, lineItemCategory, lineItemMemo, lineItemAmount );
                            item.IsReconciled = isCleared;
                            trans.LineItems.Add( item );
                            break;

                        // Investment indicators

                        case 'Y':	// security name
                            investmentName = line.Substring( 1 );
                            break;
                        case 'I':	// price
                            investmentPrice = line.Substring( 1 );
                            break;
                        case 'Q':	// quantity (shares)
                            investmentShares = line.Substring( 1 );
                            break;
                        case 'O':	// commission
                            investmentCommission = line.Substring( 1 );
                            break;

                        default:
                            // Unrecognized - ignore
                            break;
                    }

                    lineNumber++;
                }	// while

                if( trans.LineItems.Count > 0 )
                {
                    // Transaction had splits.
                    // Add the transaction amount as the final split.
                    item = BuildLineItem( ledger, trans, transCategory, trans.Memo, -transAmount );
                    item.Number = checkNumber;
                    item.Account = account;
                    item.CategoryObject = null;
                    item.IsReconciled = isCleared;
                    trans.LineItems.Add( item );
                    // Adjust total transaction amount.
                    trans.Amount = trans.GetCreditLineItemSum();
                }
                else
                {
                    // No splits. Must create a matching debit and credit.
                    Account otherAccount;
                    Category otherCategory;
                    otherAccount = ParseAccount( ledger, transCategory, transAmount, out otherCategory );
                    if( transAmount < 0 )
                    {
                        // Withdrawal
                        item = trans.CreateLineItem( TransactionType.Credit, account, Math.Abs( transAmount ), null );
                        item.Number = checkNumber;
                        item.IsReconciled = isCleared;
                        trans.LineItems.Add( item );
                        item = trans.CreateLineItem( TransactionType.Debit, otherAccount, Math.Abs( transAmount ), otherCategory );
                        trans.LineItems.Add( item );
                    }
                    else
                    {
                        // Deposit
                        item = trans.CreateLineItem( TransactionType.Credit, otherAccount, Math.Abs( transAmount ), otherCategory );
                        trans.LineItems.Add( item );
                        item = trans.CreateLineItem( TransactionType.Debit, account, Math.Abs( transAmount ), null );
                        item.IsReconciled = isCleared;
                        trans.LineItems.Add( item );
                    }
                }

                trans.LineItems.Sort();
                trans.Validate();

                // Special case "Opening Balance" line:
                if( trans.Description == "Opening Balance" && account.GetLineItems().Count == 0 )
                {
                    // The account name is given in the category
                    account.Name = transCategory.Trim( '[', ']' );
                    account.StartingBalance = account.IsLiability ? -transAmount : transAmount;
                    account.StartingDate = trans.Date;
                }
                else
                {
                    trans.Id = ledger.GetNextTransactionId();
                    ledger.AddTransaction( trans );
                }
            }
            catch( Exception ex )
            {
                // Don't add broken transactions
                Console.WriteLine( "Error on line number " + lineNumber.ToString() );
                Console.WriteLine( ex.ToString() );
                throw;
            }

            return lineNumber;
        }
Пример #13
0
        private LineItem BuildLineItem( Ledger ledger, Transaction trans, string text, string memo, decimal amount )
        {
            // Negative amounts are made into debits
            // Positive amounts are made into credits
            // This matches the form used in QIF splits

            LineItem lineItem = null;
            Account lineItemAccount;
            Category category;

            lineItemAccount = ParseAccount( ledger, text, amount, out category );
            if( amount < 0 )
                lineItem = trans.CreateLineItem( TransactionType.Debit, lineItemAccount, Math.Abs( amount ), category );
            else
                lineItem = trans.CreateLineItem( TransactionType.Credit, lineItemAccount, Math.Abs( amount ), category );

            if( text != null && text[0] == '[' )
                lineItem.CategoryObject = null;

            lineItem.Memo = memo;

            return lineItem;
        }
Пример #14
0
 public void SaveLedger( Ledger ledger )
 {
     throw new StorageException( "Saving in the QIF format is not implemented." );
 }
Пример #15
0
    protected void SubmitPurchase_Click( object sender, EventArgs e )
    {
        try
        {
            dataMgr.Lock();
            try
            {
                // TODO: Remove obsolete DataManager code.

                // Make sure we have the very latest data file.
                ledger = dataMgr.Load( false, false );

                // Build a transaction object
                Transaction trans = new Transaction();
                trans.Id = ledger.GetNextTransactionId();
                trans.Date = DateTime.Parse( txtDate.Text );
                trans.Amount = Convert.ToDecimal( txtAmount.Text );
                trans.Description = txtPayee.Text;
                if( txtMemo.Text.Length > 0 ) trans.Memo = txtMemo.Text;

                // Populate line items
                Account account = ledger.FindAccount( Convert.ToInt32( ddlAccount.SelectedValue ) );
                LineItemCredit credit = new LineItemCredit( account, trans.Amount );
                credit.Id = ledger.GetNextLineItemId();
                credit.Transaction = trans;
                if( txtNumber.Text.Length > 0 ) credit.Number = txtNumber.Text;
                if( txtMemo.Text.Length > 0 ) credit.Memo = txtMemo.Text;
                trans.LineItems.Add( credit );

                Account expenseAccount = null;
                Category expenseCategory = null;
                if( !ledger.ParseCategory( txtCategory.Text, out expenseAccount, out expenseCategory ) )
                {
                    SubmitPurchaseStatusLabel.Text = "The category provided could not be found.";
                    return;
                }

                LineItemDebit debit = new LineItemDebit( expenseAccount, trans.Amount );
                debit.Id = ledger.GetNextLineItemId();
                debit.Transaction = trans;
                debit.CategoryObject = expenseCategory;
                trans.LineItems.Add( debit );

                // Add to ledger and save
                ledger.AddTransaction( trans );
                // HACK: Removing until we figure out how to fix this...
                // ledger.RefreshMissingTransactions();
                ledger.SortTransactions();
                dataMgr.Save( ledger );

                decimal newBalance = account.Balance;
                SubmitPurchaseStatusLabel.Text = string.Format( "Transaction #{0} saved. {2} balance {1:N2}.", trans.Id, newBalance, account.Name );

        #if DATASTORAGE
                try
                {
                    // DataLedger is initialized in BasePage.OnInit
                    DataLedger.Storage.BeginTransaction();
                    try
                    {
                        IAccount dbaccount = DataLedger.FindAccountByName( account.Name );
                        if( dbaccount == null )
                            throw new ApplicationException( "Account " + account.Name + " not found in data storage." );

                        IAccount dbexpense = DataLedger.FindAccountByName( expenseAccount.Name );
                        if( dbexpense == null )
                            throw new ApplicationException( "Expense account " + expenseAccount.Name + " not found in data storage." );

                        ITransaction dbtrans = DataLedger.Storage.CreateTransaction();
                        dbtrans.Amount = trans.Amount;
                        dbtrans.Date = trans.Date;
                        dbtrans.Memo = trans.Memo;
                        dbtrans.Payee = trans.Payee;
                        DataLedger.Storage.SaveTransaction( dbtrans );

                        ILineItem dbcredit = DataLedger.Storage.CreateLineItem( dbtrans );
                        dbcredit.AccountId = dbaccount.Id;
                        dbcredit.Amount = credit.Amount;
                        dbcredit.Category = null;
                        dbcredit.Memo = credit.Memo;
                        dbcredit.Number = credit.Number;
                        dbcredit.Type = TransactionType.Credit;
                        DataLedger.Storage.SaveLineItem( dbcredit );

                        ILineItem dbdebit = DataLedger.Storage.CreateLineItem( dbtrans );
                        dbdebit.AccountId = dbexpense.Id;
                        dbdebit.Amount = debit.Amount;
                        dbdebit.Category = debit.Category;
                        dbdebit.Type = TransactionType.Debit;
                        DataLedger.Storage.SaveLineItem( dbdebit );

                        DataLedger.Storage.CommitTransaction();

                        DataLedger.RefreshUnmatchedRecords();
                    }
                    catch( Exception )
                    {
                        DataLedger.Storage.RollbackTransaction();
                        throw;
                    }
                }
                catch( Exception ex )
                {
                    SubmitPurchaseStatusLabel.Text += "<br />An error occurred while saving to data storage: <br /><pre>" + ex.ToString() + "</pre>";
                }
        #endif

                // Reset text boxes
                txtNumber.Text = "";
                txtAmount.Text = "";
                txtPayee.Text = "";
                txtMemo.Text = "";
                txtCategory.Text = "";
            }
            finally
            {
                dataMgr.Unlock();
            }
        }
        catch( LockException ex )
        {
            SubmitPurchaseStatusLabel.Text = "The data file is in use by '" + ex.LockOwner + "'. Please try again in a moment.";
        }
        catch( Exception ex )
        {
            SubmitPurchaseStatusLabel.Text = "An error occurred while saving: " + ex.Message;
        }
    }
Пример #16
0
        private void AssignTransactionIdentifiers( Ledger ledger )
        {
            // Determine the next available Id by finding the maximum used Id
            foreach( Transaction trans in ledger.Transactions )
            {
                if( trans.Id >= ledger.NextTransactionId )
                    ledger.NextTransactionId = trans.Id + 1;
            }

            // Assign an Id to any transactions that don't have one
            foreach( Transaction trans in ledger.Transactions )
            {
                if( trans.Id == 0 )
                    trans.Id = ledger.GetNextTransactionId();
            }
        }
Пример #17
0
 public void Open()
 {
     saveOnClose = false;
     ledger = dataMgr.Load();
 }
Пример #18
0
        /// <summary>
        /// Deserialize from an XML reader into a Transaction object.
        /// </summary>
        /// <param name="xmlReader">XmlReader to deserialize from.</param>
        /// <param name="ledger">Parent Ledger object.</param>
        /// <returns>A Transaction object.</returns>
        public RecurringTransaction DeserializeRecurringTransaction( XmlReader xmlReader, Version version, Ledger ledger )
        {
            if( xmlReader.NodeType != XmlNodeType.Element || xmlReader.Name != "RecurringTransaction" )
                throw new ApplicationException( "Needs a RecurringTransaction node to deserialize a RecurringTransaction object." );

            RecurringTransaction trans = new RecurringTransaction();

            if( string.Compare( xmlReader.GetAttribute( "Enabled" ), "false", StringComparison.OrdinalIgnoreCase ) == 0 )
                trans.Enabled = false;

            bool accountShortcut = false;
            LineItem lineItem = null;

            while( xmlReader.Read() )
            {
                Account account = null;
                Category category = null;
                int accountId = 0;
                decimal amount = 0;
                string memo = null;

                switch( xmlReader.NodeType )
                {
                    case XmlNodeType.Element:
                        switch( xmlReader.Name )
                        {
                            case "StartingDate":
                                trans.StartingDate = DateTime.Parse( xmlReader.ReadString() );
                                break;
                            case "Interval":
                                string attrib;
                                attrib = xmlReader.GetAttribute( "Months" );
                                if( attrib != null ) trans.AddMonths = Convert.ToInt32( attrib );
                                attrib = xmlReader.GetAttribute( "Days" );
                                if( attrib != null ) trans.AddDays = Convert.ToInt32( attrib );
                                break;
                            case "Date":
                                trans.Date = DateTime.Parse( xmlReader.ReadString() );
                                break;
                            case "Amount":
                                trans.Amount = Decimal.Parse( xmlReader.ReadString() );
                                break;
                            case "Description":
                                trans.Description = xmlReader.ReadString();
                                break;
                            case "Memo":
                                trans.Memo = xmlReader.ReadString();
                                break;
                            case "Credit":
                                if( accountShortcut )
                                    throw new ArgumentException( "LineItems not expected when using Account/Category elements." );
                                if( xmlReader.GetAttribute( "AccountId" ) != null )
                                {
                                    accountId = Int32.Parse( xmlReader.GetAttribute( "AccountId" ) );
                                    account = ledger.FindAccount( accountId );
                                }
                                else
                                {
                                    account = ledger.FindAccount( xmlReader.GetAttribute( "Account" ) );
                                    if( account == null )
                                        throw new ArgumentException( "Reference to undefined account named " + xmlReader.GetAttribute( "Account" ) );
                                    accountId = account.Id;
                                }
                                if( xmlReader.GetAttribute( "Category" ) != null )
                                    category = account.GetCategory( xmlReader.GetAttribute( "Category" ) );
                                if( xmlReader.GetAttribute( "Amount" ) != null )
                                    amount = Decimal.Parse( xmlReader.GetAttribute( "Amount" ) );
                                else
                                    amount = trans.Amount;

                                memo = xmlReader.GetAttribute( "Memo" );

                                lineItem = new LineItemCredit( account, amount, category );
                                lineItem.Transaction = trans;
                                lineItem.Memo = memo;
                                trans.LineItems.Add( lineItem );
                                break;
                            case "Debit":
                                if( accountShortcut )
                                    throw new ArgumentException( "LineItems not expected when using Account/Category elements." );
                                if( xmlReader.GetAttribute( "AccountId" ) != null )
                                {
                                    accountId = Int32.Parse( xmlReader.GetAttribute( "AccountId" ) );
                                    account = ledger.FindAccount( accountId );
                                }
                                else
                                {
                                    account = ledger.FindAccount( xmlReader.GetAttribute( "Account" ) );
                                    if( account == null )
                                        throw new ArgumentException( "Reference to undefined account named " + xmlReader.GetAttribute( "Account" ) );
                                    accountId = account.Id;
                                }
                                if( xmlReader.GetAttribute( "Category" ) != null )
                                    category = account.GetCategory( xmlReader.GetAttribute( "Category" ) );
                                if( xmlReader.GetAttribute( "Amount" ) != null )
                                    amount = Decimal.Parse( xmlReader.GetAttribute( "Amount" ) );
                                else
                                    amount = trans.Amount;

                                memo = xmlReader.GetAttribute( "Memo" );

                                lineItem = new LineItemDebit( account, amount, category );
                                lineItem.Transaction = trans;
                                lineItem.Memo = memo;
                                trans.LineItems.Add( lineItem );
                                break;
                            default:
                                break;
                        }
                        break;

                    case XmlNodeType.EndElement:
                        switch( xmlReader.Name )
                        {
                            case "RecurringTransaction":
                                return trans;
                        }
                        break;
                }
            }

            trans.SetUnmodified();

            return trans;
        }
Пример #19
0
 /// <summary>
 /// Merges the transactions from another ledger into this ledger.
 /// </summary>
 /// <param name="newLedger">Ledger to merge transactions from.</param>
 public void Merge( Ledger newLedger )
 {
     foreach( Transaction trans in newLedger.Transactions )
     {
         Transaction newTrans = new Transaction();
         newTrans.Id = GetNextTransactionId();
         newTrans.Date = trans.Date;
         newTrans.Amount = trans.Amount;
         newTrans.Description = trans.Description;
         newTrans.Memo = trans.Memo;
         newTrans.Flags = trans.Flags;
         foreach( LineItem lineItem in trans.LineItems )
         {
             LineItem newLineItem = new LineItem();
             newLineItem.TransactionType = lineItem.TransactionType;
             newLineItem.Amount = lineItem.Amount;
             newLineItem.Memo = lineItem.Memo;
             newLineItem.Number = lineItem.Number;
             newLineItem.Account = GetAccount( lineItem.Account.AccountType, lineItem.Account.Name );
             if( lineItem.CategoryObject != null )
                 newLineItem.CategoryObject = newLineItem.Account.GetCategory( lineItem.CategoryObject.Name );
             newTrans.LineItems.Add( newLineItem );
         }
         newTrans.Validate();
         AddTransaction( newTrans );
     }
 }
Пример #20
0
        /// <summary>
        /// Deserialize from an XML reader into a Transaction object.
        /// </summary>
        /// <param name="xmlReader">XmlReader to deserialize from.</param>
        /// <param name="ledger">Parent Ledger object.</param>
        /// <returns>A Transaction object.</returns>
        public Transaction DeserializeTransaction( XmlReader xmlReader, Version version, Ledger ledger )
        {
            if( xmlReader.NodeType != XmlNodeType.Element || xmlReader.Name != "Transaction" )
                throw new ApplicationException( "Needs a Transaction node to deserialize an transaction object." );

            OnlineTransactionStorageXml ostorage = new OnlineTransactionStorageXml();
            Transaction trans = new Transaction();

            if( xmlReader.GetAttribute( "Id" ) != null )
                trans.Id = Int32.Parse( xmlReader.GetAttribute( "Id" ) );

            string voidAttribute = xmlReader.GetAttribute( "Void" );
            string pendingAttribute = xmlReader.GetAttribute( "Pending" );
            //string reconciledAttribute = xmlReader.GetAttribute( "Reconciled" );
            string recurringAttribute = xmlReader.GetAttribute( "Recurring" );

            // What's the best way to do a case-insensitive string compare?
            // Options:
            // x.Equals( y, StringComparison.OrdinalIgnoreCase)
            // x.ToLower() == y.ToLower()
            // string.Compare( x, y, true );
            // string.Compare( x, y, StringComparison.OrdinalIgnoreCase );

            if( string.Compare( voidAttribute, "true", StringComparison.OrdinalIgnoreCase ) == 0 )
                trans.Flags |= TransactionFlags.Void;
            if( string.Compare( pendingAttribute, "true", StringComparison.OrdinalIgnoreCase ) == 0 )
                trans.Flags |= TransactionFlags.Pending;
            //if( string.Compare( reconciledAttribute, "true", StringComparison.OrdinalIgnoreCase ) == 0 )
            //	trans.Flags |= TransactionFlags.Reconciled;
            if( string.Compare( recurringAttribute, "true", StringComparison.OrdinalIgnoreCase ) == 0 )
                trans.Flags |= TransactionFlags.Recurring;

            bool accountShortcut = false;
            LineItem lineItem = null;

            while( xmlReader.Read() )
            {
                string accountName = null;
                Account account = null;
                string categoryName = null;
                Category category = null;
                int accountId = 0;
                decimal amount = 0;
                string memo = null;

                switch( xmlReader.NodeType )
                {
                    case XmlNodeType.Element:
                        switch( xmlReader.Name )
                        {
                            case "Date":
                                trans.Date = DateTime.Parse( xmlReader.ReadString() );
                                break;
                            case "Amount":
                                trans.Amount = Decimal.Parse( xmlReader.ReadString() );
                                break;
                            case "Description":
                                trans.Description = xmlReader.ReadString();
                                break;
                            case "Memo":
                                trans.Memo = xmlReader.ReadString();
                                break;
                            case "Account":
                                // Shortcut for LineItems
                                accountShortcut = true;
                                accountName = xmlReader.ReadString();
                                account = ledger.FindAccount( accountName );
                                if( account == null )
                                    throw new ArgumentException( "Reference to undefined account named " + accountName );
                                lineItem = new LineItemCredit( account, trans.Amount );
                                lineItem.Transaction = trans;
                                trans.LineItems.Add( lineItem );
                                break;
                            case "AccountId":
                                // Shortcut for LineItems
                                accountShortcut = true;
                                accountId = Int32.Parse( xmlReader.ReadString() );
                                account = ledger.FindAccount( accountId );
                                if( account == null )
                                    throw new ArgumentException( "Reference to unknown account Id " + accountId );
                                lineItem = new LineItemCredit( account, trans.Amount );
                                trans.LineItems.Add( lineItem );
                                account = ledger.FindAccount( "Expenses" );
                                if( account == null )
                                    throw new ArgumentException( "Expense account not defined." );
                                lineItem = new LineItemDebit( account, trans.Amount );
                                lineItem.Transaction = trans;
                                trans.LineItems.Add( lineItem );
                                break;
                            case "Category":
                                // Shortcut for LineItems
                                if( trans.LineItems.Count == 0 )
                                    throw new ArgumentException( "Account or AccountId must be specified before Category element." );
                                LineItem[] debits = trans.GetDebitLineItems();
                                if( debits.Length != 1 )
                                    throw new ArgumentException( "Category element can only be used when there is exactly one debit line item." );
                                account = ledger.FindAccount( "Expenses" );
                                categoryName = xmlReader.ReadString();
                                category = account.GetCategory( categoryName );
                                debits[0].CategoryObject = category;
                                break;
                            case "Credit":
                                if( accountShortcut )
                                    throw new ArgumentException( "LineItems not expected when using Account/Category elements." );
                                if( version == new Version( 1, 0 ) )
                                {
                                    throw new ApplicationException( "Version 1.0 data files are no longer supported." );
                                }
                                else if( version >= new Version( 1, 1 ) )
                                {
                                    if( xmlReader.GetAttribute( "AccountId" ) != null )
                                    {
                                        accountId = Int32.Parse( xmlReader.GetAttribute( "AccountId" ) );
                                        account = ledger.FindAccount( accountId );
                                    }
                                    else
                                    {
                                        account = ledger.FindAccount( xmlReader.GetAttribute( "Account" ) );
                                        if( account == null )
                                            throw new ArgumentException( "Reference to undefined account named " + xmlReader.GetAttribute( "Account" ) );
                                        accountId = account.Id;
                                    }
                                    if( xmlReader.GetAttribute( "Category" ) != null )
                                        category = account.GetCategory( xmlReader.GetAttribute( "Category" ) );
                                    if( xmlReader.GetAttribute( "Amount" ) != null )
                                        amount = Decimal.Parse( xmlReader.GetAttribute( "Amount" ) );
                                    else
                                        amount = trans.Amount;

                                    if( version >= new Version( 1, 2 ) )
                                        memo = xmlReader.GetAttribute( "Memo" );
                                    else
                                        memo = xmlReader.ReadString();
                                }
                                lineItem = new LineItemCredit( account, amount, category );
                                lineItem.Transaction = trans;
                                lineItem.Memo = memo;
                                lineItem.Number = xmlReader.GetAttribute( "Number" );	// could be null
                                if( xmlReader.GetAttribute( "Reconciled" ) != null )
                                    lineItem.IsReconciled = (xmlReader.GetAttribute( "Reconciled" ).ToLower() == "true");
                                if( xmlReader.GetAttribute( "Id" ) != null )
                                    lineItem.Id = int.Parse( xmlReader.GetAttribute( "Id" ) );
                                trans.LineItems.Add( lineItem );
                                break;
                            case "Debit":
                                if( accountShortcut )
                                    throw new ArgumentException( "LineItems not expected when using Account/Category elements." );
                                if( version == new Version( 1, 0 ) )
                                {
                                    throw new ApplicationException( "Version 1.0 data files are no longer supported." );
                                }
                                else if( version >= new Version( 1, 1 ) )
                                {
                                    if( xmlReader.GetAttribute( "AccountId" ) != null )
                                    {
                                        accountId = Int32.Parse( xmlReader.GetAttribute( "AccountId" ) );
                                        account = ledger.FindAccount( accountId );
                                    }
                                    else
                                    {
                                        account = ledger.FindAccount( xmlReader.GetAttribute( "Account" ) );
                                        if( account == null )
                                            throw new ArgumentException( "Reference to undefined account named " + xmlReader.GetAttribute( "Account" ) );
                                        accountId = account.Id;
                                    }
                                    if( xmlReader.GetAttribute( "Category" ) != null )
                                        category = account.GetCategory( xmlReader.GetAttribute( "Category" ) );
                                    if( xmlReader.GetAttribute( "Amount" ) != null )
                                        amount = Decimal.Parse( xmlReader.GetAttribute( "Amount" ) );
                                    else
                                        amount = trans.Amount;

                                    if( version >= new Version( 1, 2 ) )
                                        memo = xmlReader.GetAttribute( "Memo" );
                                    else
                                        memo = xmlReader.ReadString();
                                }
                                lineItem = new LineItemDebit( account, amount, category );
                                lineItem.Transaction = trans;
                                lineItem.Memo = memo;
                                lineItem.Number = xmlReader.GetAttribute( "Number" );	// could be null
                                if( xmlReader.GetAttribute( "Reconciled" ) != null )
                                    lineItem.IsReconciled = (xmlReader.GetAttribute( "Reconciled" ).ToLower() == "true");
                                if( xmlReader.GetAttribute( "Id" ) != null )
                                    lineItem.Id = int.Parse( xmlReader.GetAttribute( "Id" ) );
                                trans.LineItems.Add( lineItem );
                                break;
                            case "OnlineTransaction":
                                // We need to figure out whether this is a child of
                                // the Transaction element or a Credit/Debit element.
                                // Try examining xmlReader.Depth.
                                OnlineTransaction otrans = ostorage.DeserializeTransaction( xmlReader, version );
                                if( xmlReader.Depth > 3 )
                                {
                                    // Associate with LineItem
                                    lineItem.OnlineTransaction = otrans;
                                }
                                else
                                {
                                    throw new StorageException( "OnlineTransaction nodes are no longer supported at the Transaction level." );
                                    // Associate with Transaction (deprecated)
                                    // trans.OnlineTransactions.Add( otrans );
                                }
                                break;
                            default:
                                break;
                        }
                        break;

                    case XmlNodeType.EndElement:
                        if( xmlReader.Name == "Transaction" )
                            return trans;
                        break;
                }
            }

            trans.SetUnmodified();

            return trans;
        }
Пример #21
0
 public static void Load()
 {
     ledger = manager.Load();
 }
Пример #22
0
 /// <summary>
 /// Load transactional data from the storage file if necessary.
 /// </summary>
 public Ledger Load( bool addMissingTransactions, bool sortTransactions )
 {
     if( ledger == null || cacheTime == DateTime.MinValue || ledgerStorage.FileDateTime > cacheTime )
     {
         stopwatch.Reset();
         stopwatch.Start();
         ledger = ledgerStorage.LoadLedger();
         if( addMissingTransactions ) ledger.AddMissingTransactions();
         if( sortTransactions ) ledger.SortTransactions();
         cacheTime = ledger.LoadDate;
         stopwatch.Stop();
         loadTimeMilliseconds = stopwatch.ElapsedMilliseconds;
     }
     // Extra insurance, but at a cost: this may be second time done (LoadLedger does too)
     ledgerStorage.ValidateIdentifierLinks( ledger );
     return ledger;
 }