diff --git a/apps/finance/services/api/main/handler.go b/apps/finance/services/api/main/handler.go index 6a1ab98..a5103ed 100644 --- a/apps/finance/services/api/main/handler.go +++ b/apps/finance/services/api/main/handler.go @@ -642,19 +642,20 @@ func (h *Handler) Transactions(w http.ResponseWriter, r *http.Request) { } render(w, txnsTmpl, map[string]interface{}{ - "UserID": a.UserID, - "Email": a.Email, - "Title": "Transactions", - "Route": "transactions", - "IsOwner": true, - "Txns": txns, - "Categories": cats, - "Accounts": accounts, - "AccountNames": accountNames, + "UserID": a.UserID, + "Email": a.Email, + "Title": "Transactions", + "Route": "transactions", + "IsOwner": true, + "Txns": txns, + "Categories": cats, + "Accounts": accounts, + "AccountNames": accountNames, "CategoryColors": catColors, - "Cat": cat, - "Search": search, - "Days": daysStr, + "Cat": cat, + "Search": search, + "Days": daysStr, + "Notice": r.URL.Query().Get("notice"), }) } @@ -2241,48 +2242,13 @@ func (h *Handler) Household(w http.ResponseWriter, r *http.Request) { func (h *Handler) AutoImport(w http.ResponseWriter, r *http.Request) { auth := getAuth(r) - ctx := r.Context() - - if r.Method == http.MethodPost { - _ = r.ParseForm() - sched := &ImportSchedule{ - ID: bson.NewObjectID().Hex(), - UserID: auth.UserID, - AccountID: r.FormValue("account_id"), - Label: r.FormValue("label"), - Format: r.FormValue("format"), - URL: r.FormValue("url"), - Active: r.FormValue("active") == "on", - CreatedAt: time.Now(), - } - if err := h.store.createImportSchedule(ctx, sched); err != nil { - http.Error(w, "failed to create schedule", http.StatusInternalServerError) - return - } - http.Redirect(w, r, "/auto-import", http.StatusSeeOther) - return - } - - if r.Method == http.MethodDelete { - id := r.PathValue("id") - if err := h.store.deleteImportSchedule(ctx, id, auth.UserID); err != nil { - http.Error(w, "failed to delete schedule", http.StatusInternalServerError) - return - } - w.WriteHeader(http.StatusNoContent) - return - } - - schedules, _ := h.store.getImportSchedules(ctx, auth.UserID) - accounts, _ := h.store.getAccounts(ctx, auth.UserID) - + accounts, _ := h.store.getAccounts(r.Context(), auth.UserID) render(w, autoImportTmpl, &AutoImportData{ - UserID: auth.UserID, - Email: auth.Email, - Title: "Auto Import", - Route: "/auto-import", - Accounts: accounts, - Schedules: schedules, + UserID: auth.UserID, + Email: auth.Email, + Title: "Import Guide", + Route: "/auto-import", + Accounts: accounts, }) } @@ -2320,8 +2286,6 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("POST /household", h.Household) mux.HandleFunc("DELETE /household", h.Household) mux.HandleFunc("GET /auto-import", h.AutoImport) - mux.HandleFunc("POST /auto-import", h.AutoImport) - mux.HandleFunc("DELETE /auto-import/{id}", h.AutoImport) } func sortStrings(s []string) { diff --git a/apps/finance/services/api/main/templates/auto_import.html b/apps/finance/services/api/main/templates/auto_import.html index 25c3077..3cb0f9e 100644 --- a/apps/finance/services/api/main/templates/auto_import.html +++ b/apps/finance/services/api/main/templates/auto_import.html @@ -3,110 +3,80 @@ {{define "content"}} {{$d := .}} -

Auto Import

- -
-

New Schedule

-

- Configure a recurring CSV import source. The app will fetch and import it automatically on a daily schedule. -

-
-
- - -
-
-
- - -
-
- - -
-
-
- - -
-
- - -
- -
-
- -
Active Schedules
- -{{if $d.Schedules}} -
- {{range $d.Schedules}} -
-
-
- {{.Label}} - {{if .Active}}active{{else}}paused{{end}} -
-
- Format: {{.Format}} • Account: {{.AccountID}} - {{if .URL}}• {{.URL}}{{end}} - {{if not .LastRunAt.IsZero}}• Last run: {{dateShort .LastRunAt}}{{end}} -
-
- +
+
+

Import Guide

+

How to get your bank transactions into the app

- {{end}} + Upload CSV →
-{{else}} -
No import schedules yet. Create one above.
-{{end}} -
-
Webhook endpoint
-

- You can also push a CSV file directly without scheduling. Use this endpoint from any automation tool (n8n, cron, etc.): -

-
- POST /import/confirm — multipart/form-data with fields: account_id, format, rows (JSON array) +
+
1
+
+

Export a CSV from your bank

+

Log into your bank's online portal and download a transaction extract for the period you want. Most Portuguese banks offer this under Movimentos or Extratos.

+
+
+
CGD (Caixa Geral de Depósitos)
+
Netbanco → Consultas → Movimentos de Conta → Exportar. Choose CSV.
+
+
+
Trade Republic
+
App → Profile → Documents → Activity. Export as CSV.
+
+
+
Generic / Other banks
+
Any CSV with columns: Date, Description, Amount. The importer auto-detects column order.
+
+
- +
+
2
+
+

Upload and preview

+

Go to Import, pick the account and format, then select your file. You'll see a preview of every row with auto-suggested categories. Rows already in the database are shown greyed out and will be skipped automatically — safe to re-upload the same file.

+
+
+ +
+
3
+
+

Review categories and confirm

+

Adjust any auto-categorised rows using the dropdown selectors, then click Confirm Import. Only new transactions are saved.

+
+
+ +
+ Tip — avoid duplicates: the importer fingerprints each row by date, description, amount, and account. Re-uploading a file that overlaps with a previous import is safe; duplicates are detected and skipped at both preview and confirm time. +
+ +
+

Automate with a script

+

+ If you export your CSV on a schedule (e.g. via a cron job or n8n), you can push it directly to the import endpoint without going through the UI: +

+
curl -X POST https://<your-host>/import/preview \
+  -H "X-Auth-User-Id: <user-id>" \
+  -H "X-Auth-Email: <email>" \
+  -F "account_id=<account-id>" \
+  -F "format=cgd" \
+  -F "file=@movements.csv"
+

The preview endpoint returns an HTML page; for headless import pipe the confirmed rows to POST /import/confirm with the same account_id, format, raw_data, and categories[] fields.

+
{{end}} diff --git a/apps/finance/services/api/main/templates/base.html b/apps/finance/services/api/main/templates/base.html index 3d1a7a5..0291d0e 100644 --- a/apps/finance/services/api/main/templates/base.html +++ b/apps/finance/services/api/main/templates/base.html @@ -404,7 +404,7 @@ What If Tax Household - Auto Import + Import Guide Sharing {{.Email}} diff --git a/apps/finance/services/api/main/templates/transactions.html b/apps/finance/services/api/main/templates/transactions.html index 3893f9a..67bfeea 100644 --- a/apps/finance/services/api/main/templates/transactions.html +++ b/apps/finance/services/api/main/templates/transactions.html @@ -1,5 +1,11 @@ {{define "content"}} {{$d := .}} +{{if eq $d.Notice "all_duplicates"}} +
+ + Every row in that file was already imported — nothing was added. +
+{{end}}

Transactions