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