Skip to content

Instantly share code, notes, and snippets.

@julian-klode
Created November 5, 2019 19:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save julian-klode/199f18a5419b40d39094c2721ac2e058 to your computer and use it in GitHub Desktop.
Save julian-klode/199f18a5419b40d39094c2721ac2e058 to your computer and use it in GitHub Desktop.
///bin/true; exec /usr/bin/env go run "$0" "$@"
package main
import (
"flag"
"log"
"os"
"regexp"
"runtime/pprof"
"strings"
"github.com/julian-klode/goledger"
"github.com/julian-klode/goledger/importer"
)
var accounts = map[string]string{
// IBAN : account
}
// categories maps importer categories to accounts
var categories = map[importer.Category]string{
importer.CategoryATM: "expenses:cash",
importer.CategoryFoodGroceries: "expenses:shopping:food",
importer.CategoryBarsRestaurants: "expenses:restaurants",
importer.CategoryShopping: "expenses:shopping",
importer.CategoryTransportCar: "expenses:transport",
importer.CategoryBusiness: "expenses:business",
importer.CategoryTravelHolidays: "expenses:travel",
}
// Mapping maintains a mapping from a keyword to an account
type Mapping struct {
Keyword string
Account string
}
var names = []Mapping{
{"PAYPAL", "expenses:shopping"},
{"AMAZON", "expenses:shopping"},
{"Spende", "expenses:donations"},
{"Atmosfair", "expenses:donations"},
}
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
var transactions []importer.Transaction
var seen = make(map[string]bool)
for _, arg := range os.Args {
var ts []importer.Transaction
var err error
switch {
case strings.HasSuffix(arg, ".csv") && strings.Contains(arg, "contexts"):
ts, err = importer.HBCIParseFile(arg)
case strings.HasSuffix(arg, ".csv"):
ts, err = importer.LBBParseFile(arg)
case strings.HasSuffix(arg, ".json"):
ts, err = importer.N26ParseFile(arg)
default:
continue
}
if err != nil {
panic(err)
}
seen2 := make(map[string]bool)
for k, v := range seen {
seen2[k] = v
}
for _, t := range ts {
id := t.ID()
if seen[id] {
continue
}
seen2[id] = true
transactions = append(transactions, t)
}
seen = seen2
}
for _, t := range transactions {
remote := accounts[t.RemoteAccount()]
var remote2 string
var value1 = t.Amount()
var value2 goledger.Decimal
for i := range names {
names[i].Keyword = strings.ToLower(names[i].Keyword)
}
if remote == "" {
for _, m := range names {
rem := strings.ToLower(t.RemoteName())
if strings.Contains(rem, (m.Keyword)) || strings.Contains(strings.Replace(rem, " ", "", -1), (m.Keyword)) {
remote = m.Account
continue
}
ref := strings.ToLower(t.ReferenceText())
if strings.Contains(ref, (m.Keyword)) || strings.Contains(strings.Replace(ref, " ", "", -1), (m.Keyword)) {
remote = m.Account
continue
}
}
}
// Ignore self transactions
if t.RemoteAccount() == t.LocalAccount() {
continue
}
local := accounts[t.LocalAccount()]
remoteByCategory := categories[t.Category()]
switch {
case strings.HasPrefix(t.RemoteName(), "OFD/HCC"):
remote = "income:bafög"
remote2 = "assets:loans:bafög"
value1 /= 2
value2 = value1
case strings.HasPrefix(t.RemoteName(), "GA "):
remote = "expenses:cash"
case remote == "" && remoteByCategory != "":
remote = remoteByCategory
case remote == "" && t.Amount() < 0:
remote = "expenses:misc"
case remote == "" && t.Amount() > 0:
remote = "income:misc"
// Gebühren Bondora Abhebung
case remote == "assets:bank:savings:bondora" && t.Amount() > 0:
value2 = -100
value1 += 100
remote2 = "expenses:bondora"
// Credit Agricole Zinsen
case remote == "assets:bank:savings:cacf" && t.Amount() > 0:
remote = "income:bank:cacf"
}
// We identified a transfer between two of our own bank accounts,
// add a transit node in the middle.
if strings.HasPrefix(local, "assets:bank") && strings.HasPrefix(remote, "assets:bank") && !strings.HasPrefix(remote, "assets:bank:savings") && !strings.HasPrefix(remote, "assets:bank:tomorrow") && !strings.Contains(t.ReferenceText(), "space:") {
remote = "assets:bank:transit"
}
var lt goledger.Transaction
lt.Parts = make([]goledger.TransactionPart, 0, 3)
lt.Date = t.Date()
lt.ValutaDate = t.ValutaDate()
switch {
case t.RemoteName() == "":
lt.Description = t.ReferenceText()
case t.ReferenceText() == "":
lt.Description = t.RemoteName()
default:
lt.Description = t.RemoteName() + " | " + t.ReferenceText()
}
lt.Parts = append(lt.Parts, goledger.TransactionPart{
Account: local,
Value: t.Amount(),
Currency: t.Currency(),
})
if strings.HasPrefix(lt.Description, "Debeka") {
var rules = []struct {
re *regexp.Regexp
account string
}{
{regexp.MustCompile("AuslR ([0-9,]+)"), "expenses:contracts:insurance:debeka:reise"},
{regexp.MustCompile("Kranken ([0-9,]+)"), "expenses:contracts:insurance:debeka:zahn"},
{regexp.MustCompile("Unfall (?:[0-9.]+) ([0-9,]+)"), "expenses:contracts:insurance:debeka:unfall"},
}
for _, rule := range rules {
if vals := rule.re.FindStringSubmatch(lt.Description); vals != nil {
var val goledger.Decimal
val.UnmarshalString(vals[1], ",")
lt.Parts = append(lt.Parts, goledger.TransactionPart{
Account: rule.account,
Value: val,
Currency: t.Currency(),
})
}
}
} else {
lt.Parts = append(lt.Parts, goledger.TransactionPart{
Account: remote,
Value: -value1,
Currency: t.Currency(),
})
if remote2 != "" {
lt.Parts = append(lt.Parts, goledger.TransactionPart{
Account: remote2,
Value: -value2,
Currency: t.Currency(),
})
}
}
lt.Print(os.Stdout)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment