/// <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; }
/// <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; }
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; } }