Skip to content

Commit

Permalink
mark for deletion when ynab txs not found in db txs
Browse files Browse the repository at this point in the history
  • Loading branch information
rhyek committed Jun 13, 2024
1 parent 2a43aa9 commit 665d85d
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 21 deletions.
24 changes: 8 additions & 16 deletions projects/update-ynab/banks/banks.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,7 @@ import (
"github.com/shopspring/decimal"
)

func LoadBankTxs(db *sqlx.DB) ([]types.BankAccountWithTransactions, error) {
var fromMonth time.Time
if time.Now().Day() <= 10 {
year, month, day := time.Now().Date()
fromMonth = time.Date(year, month-1, day, 0, 0, 0, 0, time.UTC)
} else {
fromMonth = time.Now()
}

func LoadBankTxs(db *sqlx.DB, fromMonth time.Time) ([]types.BankAccountWithTransactions, error) {
slog.Info("loading bank txs", slog.String("fromMonth", fromMonth.Format("2006-01")))

bankTxs := []types.DbBankTx{}
Expand Down Expand Up @@ -76,13 +68,13 @@ func LoadBankTxs(db *sqlx.DB) ([]types.BankAccountWithTransactions, error) {
bankAccounts = append(bankAccounts, *bankAccount)
}

slog.Info("printing found bank txs:")
for _, bankAccount := range bankAccounts {
for _, bankTx := range bankAccount.Transactions {
slog.Info(fmt.Sprintf("account: %s, date: %s, doc_no: %s, amount: %d", bankAccount.Account.Number,
bankTx.Date.Format("2006-01-02"), bankTx.DocNo, bankTx.Amount))
}
}
// slog.Info("printing found bank txs:")
// for _, bankAccount := range bankAccounts {
// for _, bankTx := range bankAccount.Transactions {
// slog.Info(fmt.Sprintf("account: %s, date: %s, doc_no: %s, amount: %d", bankAccount.Account.Number,
// bankTx.Date.Format("2006-01-02"), bankTx.DocNo, bankTx.Amount))
// }
// }

return bankAccounts, nil
}
13 changes: 11 additions & 2 deletions projects/update-ynab/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"log/slog"
"os"
"time"

"bank-bots/update-ynab/banks"
"bank-bots/update-ynab/types"
Expand Down Expand Up @@ -61,12 +62,20 @@ func work() (*string, error) {
return nil, err
}

bankAccountsWithTxs, err := banks.LoadBankTxs(db)
var fromMonth time.Time
if time.Now().Day() <= 10 {
year, month, day := time.Now().Date()
fromMonth = time.Date(year, month-1, day, 0, 0, 0, 0, time.UTC)
} else {
fromMonth = time.Now()
}

bankAccountsWithTxs, err := banks.LoadBankTxs(db, fromMonth)
if err != nil {
return nil, err
}

err = ynab.UpdateYnabWithBankTxs(config, bankAccountsWithTxs)
err = ynab.UpdateYnabWithBankTxs(config, bankAccountsWithTxs, fromMonth)
if err != nil {
return nil, err
}
Expand Down
73 changes: 70 additions & 3 deletions projects/update-ynab/ynab/ynab.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"log/slog"
"net/http"
"regexp"
"slices"
"time"

Expand All @@ -21,12 +22,21 @@ type AugmentedYnabTransaction struct {
ParsedMemo *ParsedYnabTransactionMemo
}

func UpdateYnabWithBankTxs(config *types.Config, bankAccountsWithTxs []types.BankAccountWithTransactions) error {
func UpdateYnabWithBankTxs(config *types.Config, bankAccountsWithTxs []types.BankAccountWithTransactions, fromMonth time.Time) error {
client := ynab.NewClient(config.YNAB.AccessToken)

ynabTxCreates := []transaction.PayloadTransaction{}
ynabTxUpdates := []transaction.PayloadTransaction{}

type DeleteTransaction struct {
Id string `json:"id"`
FlagColor transaction.FlagColor `json:"flag_color"`
}
type DeleteTransactionsPayload struct {
Transactions []DeleteTransaction `json:"transactions"`
}
ynabTxDeletes := []DeleteTransaction{}

for _, bankAccount := range bankAccountsWithTxs {
if len(bankAccount.Transactions) == 0 {
continue
Expand Down Expand Up @@ -69,9 +79,10 @@ func UpdateYnabWithBankTxs(config *types.Config, bankAccountsWithTxs []types.Ban
})
}

// upserts
for _, bankTx := range bankAccount.Transactions {
idx := slices.IndexFunc(ynabTxs, func(e AugmentedYnabTransaction) bool {
return e.ParsedMemo.Ref == bankTx.Ref || e.ParsedMemo.Ref == bankTx.DocNo
idx := slices.IndexFunc(ynabTxs, func(ynabTx AugmentedYnabTransaction) bool {
return ynabTx.ParsedMemo.Ref == bankTx.Ref || ynabTx.ParsedMemo.Ref == bankTx.DocNo
})
if idx == -1 {
slog.Info(fmt.Sprintf("creating transaction: %+v", bankTx))
Expand Down Expand Up @@ -102,6 +113,37 @@ func UpdateYnabWithBankTxs(config *types.Config, bankAccountsWithTxs []types.Ban
}
}
}
// deletes
rgx := regexp.MustCompile(`\d{8,}_\d+(\(\d+\))?$`)
isSameOrFutureMonth := func(fromMonth, t time.Time) bool {
fromYear, fromMonthNum, _ := fromMonth.Date()
tYear, tMonthNum, _ := t.Date()

if tYear > fromYear {
return true
} else if tYear == fromYear && tMonthNum >= fromMonthNum {
return true
}
return false
}
for _, ynabTx := range ynabTxs {
if !isSameOrFutureMonth(fromMonth, ynabTx.Tx.Date.Time) {
continue
}
if !rgx.MatchString(ynabTx.ParsedMemo.Ref) {
continue
}
idx := slices.IndexFunc(bankAccount.Transactions, func(bankTx types.PreparedBankTx) bool {
return ynabTx.ParsedMemo.Ref == bankTx.Ref || ynabTx.ParsedMemo.Ref == bankTx.DocNo
})
if idx == -1 {
slog.Warn(fmt.Sprintf("marking transaction for deletion: %+v", ynabTx.Tx), "ref", ynabTx.ParsedMemo.Ref)
ynabTxDeletes = append(ynabTxDeletes, DeleteTransaction{
Id: ynabTx.Tx.ID,
FlagColor: transaction.FlagColorRed,
})
}
}
}

slog.Info(fmt.Sprintf("creating %v transactions", len(ynabTxCreates)))
Expand All @@ -124,6 +166,31 @@ func UpdateYnabWithBankTxs(config *types.Config, bankAccountsWithTxs []types.Ban
return err
}
}
slog.Info(fmt.Sprintf("deleting %v transactions", len(ynabTxDeletes)))
if len(ynabTxDeletes) > 0 {
payload := DeleteTransactionsPayload{
Transactions: ynabTxDeletes,
}
jsonStr, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequest(
"PATCH",
fmt.Sprintf("https://api.ynab.com/v1/budgets/%s/transactions", config.YNAB.BudgetID),
bytes.NewBuffer(jsonStr))
if err != nil {
return err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", config.YNAB.AccessToken))
req.Header.Add("Content-Type", "application/json")
httpClient := &http.Client{}
res, err := httpClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
}

return nil
}
Expand Down

0 comments on commit 665d85d

Please sign in to comment.