-
Notifications
You must be signed in to change notification settings - Fork 0
/
ynab.go
146 lines (130 loc) · 3.57 KB
/
ynab.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package main
import (
"context"
"fmt"
"net/http"
"time"
"go.bmvs.io/ynab/api"
"go.bmvs.io/ynab/api/account"
"go.bmvs.io/ynab/api/category"
"go.bmvs.io/ynab/api/transaction"
"github.com/cnf/structhash"
"github.com/satraul/bca-go"
"github.com/shopspring/decimal"
"go.bmvs.io/ynab"
"github.com/pkg/errors"
)
func createYNABTransactions(yc ynab.ClientServicer, trxs []bca.Entry, account *account.Account, budget string) error {
ps := make([]transaction.PayloadTransaction, 0)
for _, trx := range trxs {
ps = append(ps, toPayloadTransaction(trx, account.ID))
}
resp, err := yc.Transaction().CreateTransactions(budget, ps)
if err != nil {
return err
}
if len(resp.DuplicateImportIDs) > 0 {
fmt.Printf("%d transaction(s) already exists\n", len(resp.DuplicateImportIDs))
}
fmt.Printf("%d transaction(s) were successfully created\n", len(resp.TransactionIDs))
return nil
}
func getYNABAccount(yc ynab.ClientServicer, budget string, accountName string) (*account.Account, error) {
accs, err := yc.Account().GetAccounts(budget, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to get ynab accounts. try -r")
}
for _, acc := range accs.Accounts {
if acc.Name == accountName {
return acc, nil
}
}
return nil, errors.New("couldnt find account " + accountName)
}
func createYNABBalanceAdjustment(bal bca.Balance, ctx context.Context, auth []*http.Cookie, yc ynab.ClientServicer, budget string, a *account.Account) error {
anew, err := yc.Account().GetAccount(budget, a.ID)
if err != nil {
return errors.Wrap(err, "failed to get ynab account")
}
delta := bal.Balance.IntPart()*1000 - anew.Balance
if delta != 0 {
var (
payee = "Automated Balance Adjustment"
)
c, err := func() (*category.Category, error) {
cs, err := yc.Category().GetCategories(budget, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to get categories")
}
for _, group := range cs.GroupWithCategories {
for _, c := range group.Categories {
if c.Name == "Inflows" {
return c, nil
}
}
}
return nil, errors.New("couldnt find to be budgeted category")
}()
if err != nil {
return err
}
_, err = yc.Transaction().CreateTransaction(budget, transaction.PayloadTransaction{
AccountID: a.ID,
Date: api.Date{
Time: time.Now(),
},
Amount: delta,
Cleared: transaction.ClearingStatusReconciled,
Approved: true,
PayeeID: nil,
PayeeName: &payee,
CategoryID: &c.ID,
Memo: nil,
FlagColor: nil,
ImportID: nil,
})
if err != nil {
return errors.Wrap(err, "failed to create balance adjustment transaction")
}
fmt.Printf("balance adjustment transaction successfully created\n")
}
return nil
}
func toPayloadTransaction(trx bca.Entry, accountID string) transaction.PayloadTransaction {
// description unreliable for hash
desc := trx.Description
trx.Description = ""
// use predicted clearance date for PEND transactions hash
if trx.Date.IsZero() {
trx.Date = clearDate(time.Now())
}
var (
t = trx.Date
miliunit = trx.Amount.Mul(decimal.NewFromInt(1000)).IntPart()
payee = trx.Payee
memo = desc
importid, _ = structhash.Hash(trx, 1)
)
if t.After(time.Now()) {
t = time.Now()
}
if trx.Type == "DB" {
miliunit = -miliunit
}
p := transaction.PayloadTransaction{
AccountID: accountID,
Date: api.Date{
Time: t,
},
Amount: miliunit,
Cleared: transaction.ClearingStatusCleared,
Approved: true,
PayeeID: nil,
PayeeName: &payee,
CategoryID: nil,
Memo: &memo,
FlagColor: nil,
ImportID: &importid,
}
return p
}