support for NAB
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Lynne Megido 2020-09-27 15:27:16 +10:00
parent 0800fa8cca
commit 42be0862ca
Signed by: lynnesbian
GPG key ID: F0A184B5213D9F90
2 changed files with 54 additions and 29 deletions

1
.gitignore vendored
View file

@ -2,5 +2,6 @@
bin/ bin/
obj/ obj/
test.csv test.csv
test2.csv
rules.csv rules.csv
.~lock* .~lock*

View file

@ -22,7 +22,9 @@ using System.Globalization;
using System.IO; using System.IO;
using System.CommandLine.DragonFruit; using System.CommandLine.DragonFruit;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using CsvHelper; using CsvHelper;
using CsvHelper.Configuration;
namespace BunyMuny { namespace BunyMuny {
internal class Program { internal class Program {
@ -42,34 +44,49 @@ namespace BunyMuny {
rules = ruleCsv.GetRecords<Rule>().ToList(); rules = ruleCsv.GetRecords<Rule>().ToList();
} }
// get the first line of the CSV file (the header) as a string
string header;
using (var headerReader = new StreamReader(file)) {
header = headerReader.ReadLine();
}
using var sr = new StreamReader(file); using var sr = new StreamReader(file);
using var csv = new CsvReader(sr, CultureInfo.InvariantCulture); using var csv = new CsvReader(sr, CultureInfo.InvariantCulture);
csv.Configuration.HasHeaderRecord = false;
csv.Read(); var nabRegex = new Regex(@"^\d\d \w{3} \d\d");
csv.ReadHeader();
// get the first line of the CSV file (the header) as a string if (header == null) {
var header = csv.Parser.Context.RawRecord.Trim();
switch (header) {
case null:
Console.WriteLine("File is empty 0uo"); Console.WriteLine("File is empty 0uo");
return 1; return 1;
case "Date,Description,Debits and credits,Balance": }
else if (header == "Date,Description,Debits and credits,Balance") {
bank = Bank.ME; bank = Bank.ME;
break; csv.Configuration.HasHeaderRecord = true;
case "Whatever NAB uses I guess": // read in the header to allow for accessing fields by name rather than by index
csv.Read();
csv.ReadHeader();
}
else if (nabRegex.IsMatch(header)) {
// NAB exports don't have headers
// records look like "02 Apr 20,23.00,,,MISCELLANEOUS DEBIT,V1234 02/09 PAYPAL Sydney,1234.56
// the columns mean: date of transaction, amount, ???, ???, category,method,remaining balance
// i don't like it >:c
bank = Bank.NAB; bank = Bank.NAB;
break; }
default: else {
Console.WriteLine($"Unknown header: [{header}]"); Console.WriteLine($"Unknown header: [{header}]");
break;
} }
while (csv.Read()) { while (csv.Read()) {
decimal value;
string category;
string description;
switch (bank) { switch (bank) {
case Bank.ME: case Bank.ME:
var value = decimal.Parse(csv.GetField("Debits and credits").TrimStart().Replace("$", "")); value = decimal.Parse(csv.GetField("Debits and credits").TrimStart().Replace("$", ""));
var (category, description) = MatchAgainstRules(rules, csv.GetField("Description")); (category, description) = MatchAgainstRules(rules, csv.GetField("Description"));
statements.Add(new Statement() { statements.Add(new Statement() {
Date = DateTime.ParseExact(csv.GetField("Date"), "dd/MM/yyyy", CultureInfo.InvariantCulture), Date = DateTime.ParseExact(csv.GetField("Date"), "dd/MM/yyyy", CultureInfo.InvariantCulture),
@ -81,8 +98,17 @@ namespace BunyMuny {
break; break;
case Bank.NAB: case Bank.NAB:
Console.WriteLine("Unimplemented"); // return 1;
return 1; value = decimal.Parse(csv.GetField(1));
(category, description) = MatchAgainstRules(rules, csv.GetField(5));
statements.Add(new Statement() {
Date = DateTime.ParseExact(csv.GetField(0), "dd MMM yy", CultureInfo.CurrentCulture),
OriginalDescription = csv.GetField(5),
Description = description,
Category = category,
Value = value
});
break;
case Bank.Other: case Bank.Other:
Console.WriteLine("Unknown bank!"); Console.WriteLine("Unknown bank!");
@ -102,16 +128,14 @@ namespace BunyMuny {
Console.WriteLine("Summary:"); Console.WriteLine("Summary:");
Console.WriteLine("=================="); Console.WriteLine("==================");
var summaries = statements. var summaries = statements.GroupBy(s => s.Category). // group statements by category
GroupBy(s => s.Category). // group statements by category Select(summary => new {
Select(summary => new { // and then select: // and then select:
Name = summary.First().Category ?? "Other", // the name of the category... Name = summary.First().Category ?? "Other", // the name of the category...
Total = summary.Sum(s => s.Value).ToString() // ...and the sum of all the statement's values in that category Total = summary.Sum(s => s.Value).ToString() // ...and the sum of all the statement's values in that category
}); });
var longestCategoryName = summaries. var longestCategoryName = summaries.ToList().Max(summary => summary.Name.Length);
ToList().
Max(summary => summary.Name.Length);
foreach (var summary in summaries) { foreach (var summary in summaries) {
Console.WriteLine("{0}: {1}", summary.Name.PadLeft(longestCategoryName), summary.Total); Console.WriteLine("{0}: {1}", summary.Name.PadLeft(longestCategoryName), summary.Total);