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; }
public void Close() { if( saveOnClose ) { dataMgr.Lock(); dataMgr.Save( ledger ); dataMgr.Unlock(); } ledger = null; }
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(); } }
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; } }
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 ) ); } }
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(); } }
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; }
/// <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; }
static GlobalWebData() { manager = new DataManager(); ledger = null; }
public void Close() { if( saveOnClose ) dataMgr.Save( ledger ); ledger = null; }
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; }
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; }
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; }
public void SaveLedger( Ledger ledger ) { throw new StorageException( "Saving in the QIF format is not implemented." ); }
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; } }
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(); } }
public void Open() { saveOnClose = false; ledger = dataMgr.Load(); }
/// <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> /// 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 ); } }
/// <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; }
public static void Load() { ledger = manager.Load(); }
/// <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; }