mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 07:48:47 +02:00
Add EUR currency support for international transactions
All checks were successful
Deploy to VPS / deploy (push) Successful in 50s
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:
24
CLAUDE.md
24
CLAUDE.md
@@ -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`
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -35,6 +35,7 @@ class TransactionSource(str, enum.Enum):
|
||||
class Currency(str, enum.Enum):
|
||||
CRC = "CRC"
|
||||
USD = "USD"
|
||||
EUR = "EUR"
|
||||
BTC = "BTC"
|
||||
XMR = "XMR"
|
||||
|
||||
|
||||
97
docs/WealthySmart_ BAC Pensions Statements parser.json
Normal file
97
docs/WealthySmart_ BAC Pensions Statements parser.json
Normal 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": []
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 })}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user