Add EUR currency support for international transactions
All checks were successful
Deploy to VPS / deploy (push) Successful in 50s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-04-07 20:29:37 -06:00
parent 4da00750a8
commit efe6d88286
7 changed files with 134 additions and 1 deletions

View File

@@ -13,9 +13,33 @@ Personal finance management web app.
cd frontend && pnpm install && pnpm run dev
```
## Local Docker
```bash
# Backend + DB containers
docker exec wealthysmart-db-dev psql -U wealthy_user -d wealthysmart -c 'SQL;'
```
## Deployment
- Deployed via Gitea Actions (self-hosted runner on VPS)
- Push to `main` triggers: GitHub → webhook → Gitea mirror sync → Actions workflow → Docker build & deploy
- Domain: wealth.cescalante.dev
- Reverse proxy: nginx-proxy + acme-companion (auto TLS)
## Infrastructure
- **Single server**: `ssh old-vps` — runs everything (WealthySmart, n8n, Forgejo, Vaultwarden, nginx-proxy)
- `ssh production` is **NOT valid** — do not use
- n8n UI: https://n8n.cescalante.dev — n8n DB queryable via `docker exec portfolio-db-prod psql -U portfolio_user -d n8n`
## n8n Flows
Four automated flows on old-vps feed data into WealthySmart:
1. **BAC Credit Card** — Gmail trigger → POST /transactions/
2. **Salary Deposits** — Gmail trigger → POST /transactions/
3. **Municipal Receipts** — Cron trigger → POST /municipal-receipts/upload
4. **Pension PDFs** (`e88c3UhBeo9WCbcy`) — Gmail trigger (daily midnight) → POST /pensions/upload
Flow export: `docs/WealthySmart_ BAC Pensions Statements parser.json`

View File

@@ -194,7 +194,8 @@ def create_transaction(
session.refresh(tx)
# Send push notification
symbol = "" if tx.currency == Currency.CRC else tx.currency.value
symbols = {Currency.CRC: "", Currency.USD: "$", Currency.EUR: ""}
symbol = symbols.get(tx.currency, tx.currency.value)
amount_str = f"{symbol}{tx.amount:,.0f}" if tx.currency == Currency.CRC else f"{symbol}{tx.amount:,.2f}"
is_deposit = tx.transaction_type == TransactionType.DEPOSITO
send_push_to_all(

View File

@@ -23,6 +23,12 @@ def run_migrations():
except Exception:
conn.rollback()
try:
conn.execute(text("ALTER TYPE currency ADD VALUE IF NOT EXISTS 'EUR'"))
conn.commit()
except Exception:
conn.rollback()
def get_session():
with Session(engine) as session:

View File

@@ -35,6 +35,7 @@ class TransactionSource(str, enum.Enum):
class Currency(str, enum.Enum):
CRC = "CRC"
USD = "USD"
EUR = "EUR"
BTC = "BTC"
XMR = "XMR"

View File

@@ -0,0 +1,97 @@
{
"name": "WealthySmart: BAC Pensions Statements parser",
"nodes": [
{
"parameters": {
"pollTimes": {
"item": [
{
"hour": 0
}
]
},
"simple": false,
"filters": {
"q": "Estado de cuenta Pensiones BAC CREDOMATIC -{tarjeta} "
},
"options": {
"downloadAttachments": true
}
},
"type": "n8n-nodes-base.gmailTrigger",
"typeVersion": 1.3,
"position": [
0,
0
],
"id": "bc94143f-9980-4b94-9cd7-8e206dc1232e",
"name": "Gmail Trigger",
"credentials": {
"gmailOAuth2": {
"id": "LkAGMVgAqsiCkMFX",
"name": "Gmail account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://wealth.cescalante.dev/api/v1/pensions/upload",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"parameterType": "formBinaryData",
"name": "files",
"inputDataFieldName": "attachment_0"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
208,
0
],
"id": "b73c0182-8d56-48b3-b37e-c4dd9361a062",
"name": "HTTP Request",
"credentials": {
"httpBearerAuth": {
"id": "d4Le4M5bCuhEb2NN",
"name": "BAC Parser - wealth.cescalante.dev"
}
}
}
],
"pinData": {},
"connections": {
"Gmail Trigger": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "f0bbb681-1cf3-49f4-b06e-de0218b7ff3e",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "f446bf52a953467f756f2c188ff6b09473528d6316ecb3b9568cdbfbaa3258f3"
},
"id": "e88c3UhBeo9WCbcy",
"tags": []
}

View File

@@ -151,6 +151,7 @@ export default function TransactionModal({ transaction, source, onClose, onSaved
<SelectContent>
<SelectItem value="CRC">CRC ()</SelectItem>
<SelectItem value="USD">USD ($)</SelectItem>
<SelectItem value="EUR">EUR ()</SelectItem>
</SelectContent>
</Select>
</div>

View File

@@ -5,6 +5,9 @@ export function formatAmount(amount: number, currency: string) {
if (currency === 'USD') {
return `$${abs.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}
if (currency === 'EUR') {
return `${abs.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}
return `${abs.toLocaleString('es-CR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}