feat: multi-account Fio sync + switch QR default to 2502035405/2010
All checks were successful
Deploy to K8s / deploy (push) Successful in 24s
All checks were successful
Deploy to K8s / deploy (push) Successful in 24s
Add second Fio account (CZ0820100000002502035405 / 2502035405/2010). Both accounts are fetched on every sync run and combined before dedup, so the payments sheet accumulates transactions from either account. QR codes now default to the new account. Go: - config.go: hardcoded Accounts/LoadedAccount slice replaces scalar BankAccount + FioAPIToken; Config.BankAccount renamed QRAccount; per-account tokens via FIO_API_TOKEN_NEW / FIO_API_TOKEN_OLD - banksync.SyncToSheets: accepts []fio.Client, loops to combine txns - cmd/fuj/main.go: buildFioClients helper; both sync call sites updated - html_handler + build_adults/juniors: use Config.QRAccount - New TestSyncToSheets_MultiAccount covers cross-account dedup Python: - config.py: ACCOUNTS list + LOADED_ACCOUNTS (tokens from env) - fio_utils.py: fetch_transactions_for (per-account) + fetch_transactions_all (loops all accounts) - sync_fio_to_sheets.py: uses fetch_transactions_all Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ func TestSyncToSheets_EmptySheet(t *testing.T) {
|
||||
}}
|
||||
fioFake := &fio.Fake{Transactions: testFioTxns}
|
||||
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30})
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", []fio.Client{fioFake}, sh, SyncOpts{Days: 30})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func TestSyncToSheets_Dedup(t *testing.T) {
|
||||
}}
|
||||
fioFake := &fio.Fake{Transactions: testFioTxns}
|
||||
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30})
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", []fio.Client{fioFake}, sh, SyncOpts{Days: 30})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func TestSyncToSheets_NoNewTxns(t *testing.T) {
|
||||
}}
|
||||
fioFake := &fio.Fake{Transactions: testFioTxns}
|
||||
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30})
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", []fio.Client{fioFake}, sh, SyncOpts{Days: 30})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func TestSyncToSheets_MissingHeader(t *testing.T) {
|
||||
}}
|
||||
fioFake := &fio.Fake{Transactions: testFioTxns[:1]}
|
||||
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30})
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", []fio.Client{fioFake}, sh, SyncOpts{Days: 30})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func TestSyncToSheets_Sort(t *testing.T) {
|
||||
sh := &sheets.Fake{Values: map[string][][]any{"SHEETID/A1:K": {}}}
|
||||
fioFake := &fio.Fake{Transactions: testFioTxns[:1]}
|
||||
|
||||
_, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30, Sort: true})
|
||||
_, err := SyncToSheets(context.Background(), "SHEETID", []fio.Client{fioFake}, sh, SyncOpts{Days: 30, Sort: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func TestSyncToSheets_ExplicitDateWindow(t *testing.T) {
|
||||
|
||||
from := time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC)
|
||||
to := time.Date(2026, 4, 30, 0, 0, 0, 0, time.UTC)
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{From: from, To: to})
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", []fio.Client{fioFake}, sh, SyncOpts{From: from, To: to})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -131,7 +131,7 @@ func TestSyncToSheets_DryRun(t *testing.T) {
|
||||
sh := &sheets.Fake{Values: map[string][][]any{"SHEETID/A1:K": {}}}
|
||||
fioFake := &fio.Fake{Transactions: testFioTxns}
|
||||
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh,
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", []fio.Client{fioFake}, sh,
|
||||
SyncOpts{Days: 30, Sort: true, DryRun: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -144,6 +144,40 @@ func TestSyncToSheets_DryRun(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncToSheets_MultiAccount(t *testing.T) {
|
||||
txnsA := []fio.Transaction{
|
||||
{Date: "2026-04-10", Amount: 700, Sender: "Alice", Message: "april", VS: "1", BankID: "A1"},
|
||||
}
|
||||
txnsB := []fio.Transaction{
|
||||
{Date: "2026-04-11", Amount: 500, Sender: "Bob", Message: "duben", VS: "2", BankID: "B1"},
|
||||
}
|
||||
// One transaction that duplicates the first one from account A (same sync_id).
|
||||
dupID := syncIDFor(txnsA[0])
|
||||
sh := &sheets.Fake{Values: map[string][][]any{
|
||||
"SHEETID/A1:K": {
|
||||
{"Date", "Amount", "manual fix", "Person", "Purpose", "Inferred Amount", "Sender", "VS", "Message", "Bank ID", "Sync ID"},
|
||||
{"2026-04-10", 700.0, "", "", "", "", "Alice", "1", "april", "A1", dupID},
|
||||
},
|
||||
}}
|
||||
fakeA := &fio.Fake{Transactions: txnsA}
|
||||
fakeB := &fio.Fake{Transactions: txnsB}
|
||||
|
||||
n, err := SyncToSheets(context.Background(), "SHEETID", []fio.Client{fakeA, fakeB}, sh, SyncOpts{Days: 30})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 1 {
|
||||
t.Errorf("want 1 new row (B1 from account B; A1 is duplicate), got %d", n)
|
||||
}
|
||||
if len(sh.Appended) != 1 || len(sh.Appended[0].Rows) != 1 {
|
||||
t.Fatalf("want exactly 1 row appended, got %v", sh.Appended)
|
||||
}
|
||||
row := sh.Appended[0].Rows[0]
|
||||
if row[6] != "Bob" {
|
||||
t.Errorf("expected Bob's row, got sender=%v", row[6])
|
||||
}
|
||||
}
|
||||
|
||||
// syncIDFor mirrors what SyncToSheets computes for a given fio.Transaction.
|
||||
func syncIDFor(tx fio.Transaction) string {
|
||||
currency := tx.Currency
|
||||
|
||||
Reference in New Issue
Block a user