Exemplo n.º 1
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;
        }
Exemplo n.º 2
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;
        }
Exemplo n.º 3
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;
        }
Exemplo n.º 4
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;
        }
    }