feat(go): IO layer behind interfaces (M4)
All checks were successful
Deploy to K8s / deploy (push) Successful in 11s
All checks were successful
Deploy to K8s / deploy (push) Successful in 11s
- io/attendance: CSV-over-public-URL client + Fake for adult/junior tabs - io/drive: Drive v3 modifiedTime client + Fake - io/sheets: Sheets v4 client (GetValues/AppendValues/BatchUpdateValues/ WriteHeader/SortByDateColumn) + Fake with call-capture - io/cache: Drive-modifiedTime-gated FileCache; two TTL knobs; atomic writes; generic Get[T]; Python-compatible JSON format; Flush() - io/fio: Client interface backed by Fio REST API (apiClient) and HTML scraper (transparentClient); Fake; testdata fixtures - membership/sources: NewSources wires attendance CSV + Sheets + cache into LoadAdults/LoadJuniors/LoadTransactions/LoadExceptions; Czech month parsing + merged-month maps - banksync: SyncToSheets (SHA-256 dedup, optional sort) and InferPayments ([?] review prefix, dry-run) — tested with fakes - cmd/fuj: sync and infer subcommands wired; fees and reconcile use real NewSources; go.mod gains google.golang.org/api + x/net - gofumpt extra-rules applied across all packages; lint clean Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,10 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"fuj-management/go/internal/config"
|
||||
"fuj-management/go/internal/io/fio"
|
||||
"fuj-management/go/internal/io/sheets"
|
||||
"fuj-management/go/internal/logging"
|
||||
"fuj-management/go/internal/services/banksync"
|
||||
"fuj-management/go/internal/services/membership"
|
||||
"fuj-management/go/internal/web"
|
||||
"os"
|
||||
@@ -36,9 +39,10 @@ func main() {
|
||||
feesCmd(args)
|
||||
case "reconcile":
|
||||
reconcileCmd(args)
|
||||
case "sync", "infer":
|
||||
fmt.Fprintf(os.Stderr, "fuj %s: not implemented yet (lands in M4)\n", cmd)
|
||||
os.Exit(2)
|
||||
case "sync":
|
||||
syncCmd(args)
|
||||
case "infer":
|
||||
inferCmd(args)
|
||||
case "-h", "--help", "help":
|
||||
usage()
|
||||
default:
|
||||
@@ -84,8 +88,14 @@ func feesCmd(args []string) {
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
sources := membership.NewStubSources()
|
||||
if err := membership.FeesReport(context.Background(), sources, os.Stdout); err != nil {
|
||||
ctx := context.Background()
|
||||
cfg := config.Load()
|
||||
sources, err := membership.NewSources(ctx, cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj fees: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := membership.FeesReport(ctx, sources, os.Stdout); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj fees: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -101,8 +111,14 @@ func reconcileCmd(args []string) {
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
sources := membership.NewStubSources()
|
||||
if err := membership.ReconcileReport(context.Background(), sources, time.Now().Year(), os.Stdout); err != nil {
|
||||
ctx := context.Background()
|
||||
cfg := config.Load()
|
||||
sources, err := membership.NewSources(ctx, cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj reconcile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := membership.ReconcileReport(ctx, sources, time.Now().Year(), os.Stdout); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj reconcile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -112,6 +128,91 @@ func versionCmd() {
|
||||
fmt.Printf("fuj %s (%s) built %s\n", version, commit, buildDate)
|
||||
}
|
||||
|
||||
func syncCmd(args []string) {
|
||||
fs := flag.NewFlagSet("sync", flag.ExitOnError)
|
||||
days := fs.Int("days", 30, "look-back window in days (ignored when --from/--to are set)")
|
||||
fromStr := fs.String("from", "", "start date YYYY-MM-DD")
|
||||
toStr := fs.String("to", "", "end date YYYY-MM-DD")
|
||||
sort := fs.Bool("sort", true, "sort sheet by date after appending")
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintln(os.Stderr, "usage: fuj sync [--days N] [--from YYYY-MM-DD --to YYYY-MM-DD] [--sort]")
|
||||
fs.PrintDefaults()
|
||||
}
|
||||
if err := fs.Parse(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := config.Load()
|
||||
|
||||
sheetsCli, err := sheets.New(ctx, cfg.CredentialsPath, cfg.DriveTimeout)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj sync: sheets client: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fioCli := fio.New(cfg.FioAPIToken, config.IBANAccountNum(cfg.BankAccount), nil)
|
||||
|
||||
opts := banksync.SyncOpts{Days: *days, Sort: *sort}
|
||||
if *fromStr != "" && *toStr != "" {
|
||||
opts.From, err = time.Parse("2006-01-02", *fromStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj sync: invalid --from: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
opts.To, err = time.Parse("2006-01-02", *toStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj sync: invalid --to: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
n, err := banksync.SyncToSheets(ctx, config.PaymentsSheetID, fioCli, sheetsCli, opts)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj sync: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Synced %d new transaction(s).\n", n)
|
||||
}
|
||||
|
||||
func inferCmd(args []string) {
|
||||
fs := flag.NewFlagSet("infer", flag.ExitOnError)
|
||||
dryRun := fs.Bool("dry-run", false, "print planned updates without writing to the sheet")
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintln(os.Stderr, "usage: fuj infer [--dry-run]")
|
||||
fs.PrintDefaults()
|
||||
}
|
||||
if err := fs.Parse(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := config.Load()
|
||||
|
||||
sheetsCli, err := sheets.New(ctx, cfg.CredentialsPath, cfg.DriveTimeout)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj infer: sheets client: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
sources, err := membership.NewSources(ctx, cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj infer: sources: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
n, err := banksync.InferPayments(ctx, config.PaymentsSheetID, sheetsCli, sources, banksync.InferOpts{DryRun: *dryRun})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fuj infer: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if *dryRun {
|
||||
fmt.Printf("Dry run: would update %d row(s).\n", n)
|
||||
} else {
|
||||
fmt.Printf("Updated %d row(s).\n", n)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, `usage: fuj <command> [flags]
|
||||
|
||||
@@ -120,6 +221,6 @@ Commands:
|
||||
version Print version information
|
||||
fees Calculate monthly fees
|
||||
reconcile Show balance report
|
||||
sync Sync Fio transactions [M4]
|
||||
infer Infer payment details [M4]`)
|
||||
sync Sync Fio transactions to payments sheet
|
||||
infer Infer payment details in payments sheet`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user