Last active
May 13, 2024 20:45
-
-
Save darsh12/4e93abfc6a6307c6ae0933e325f2084f to your computer and use it in GitHub Desktop.
Firefly-SimpleFIN-N8N
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "Firefly SimpleFIN Git", | |
"nodes": [ | |
{ | |
"parameters": { | |
"message": "={{ $json.errors.join() }}", | |
"additionalFields": { | |
"priority": 10 | |
}, | |
"options": { | |
"contentType": "text/plain" | |
} | |
}, | |
"id": "e8791c55-7fa8-4c0a-a50f-463cfdf7ce9b", | |
"name": "Gotify", | |
"type": "n8n-nodes-base.gotify", | |
"typeVersion": 1, | |
"position": [ | |
960, | |
100 | |
], | |
"credentials": { | |
"gotifyApi": { | |
"id": "4WPYQ0VlpLcqQuKY", | |
"name": "Gotify account" | |
} | |
} | |
}, | |
{ | |
"parameters": { | |
"jsCode": "return $input.all();" | |
}, | |
"id": "e4fb564e-f429-44a4-9c20-004ea66f6d8c", | |
"name": "Code", | |
"type": "n8n-nodes-base.code", | |
"typeVersion": 2, | |
"position": [ | |
1440, | |
360 | |
] | |
}, | |
{ | |
"parameters": { | |
"jsCode": "const orgIds = [\n { id:\"ACT-001122-1122-334n-0009-123456780dfg\", bank:\"11\"}, // Bank Account\n \n];\n\nlet transactions = [];\n\norgIds.forEach(org => {\n let account = $input.all()[0].json.accounts.find(account => account.id === org.id);\n\n if(!account) { // if no matching account found, skip the current iteration\n return;\n }\n\n let orgTransactions = account.transactions; \n orgTransactions.forEach((item)=>{\n item.bank = org.bank;\n const amount = Number(item.amount);\n\n // If the 'organization' is not an investment account, then we follow this logic.\n if (!org.investment){\n if (amount < 0){\n item.amount = Math.abs(amount).toString();\n item.type = \"withdrawal\";\n item.source_id = org.bank;\n } else {\n item.type=\"deposit\";\n item.destination_id = org.bank;\n }\n }else{\n // If the 'organization' is an investment account, then a negative amount means there is a deposit and we indicate it as such\n if (amount < 0){\n item.amount = Math.abs(amount).toString();\n item.type = \"deposit\";\n item.source_id = org.bank;\n }\n }\n });\n\n transactions = transactions.concat(orgTransactions);\n});\n\nreturn transactions;" | |
}, | |
"id": "f443321a-b974-4d70-b57d-73fa22dcdcbc", | |
"name": "Transactions", | |
"type": "n8n-nodes-base.code", | |
"typeVersion": 2, | |
"position": [ | |
760, | |
480 | |
] | |
}, | |
{ | |
"parameters": { | |
"language": "python", | |
"pythonCode": "import json\nfrom collections import defaultdict\nfrom datetime import datetime, timedelta \n\n# We have added keywords, because there maybe some transactions that have different 'banks', made on the same day and\n# match the amount. Hence, we only want transactions that contain these keywords\nkeywords = ['transfer', 'zelle']\n# Amount in days to consider for an item transfer\ntimeDeltaDays=3\n\nclass Transaction:\n def __init__(self, id, type, amount, bank, source_id, destination_id, posted, description, payee, transacted_at):\n self.id = id\n self.type = type\n self.amount = float(amount)\n self.bank = bank\n self.source_id = source_id\n self.destination_id = destination_id\n self.posted = datetime.fromtimestamp(posted)\n self.description = description.strip()\n self.payee = payee\n self.transacted_at = datetime.fromtimestamp(transacted_at)\n\n def to_dict(self):\n return {\n 'id': self.id,\n 'type': self.type,\n 'amount': self.amount,\n 'bank': self.bank,\n 'source_id': self.source_id,\n 'destination_id': self.destination_id,\n 'posted': self.posted.timestamp(),\n 'description': self.description,\n 'payee': self.payee,\n 'transacted_at': self.transacted_at.timestamp(),\n }\n\nclass Transfer:\n def __init__(self, id, amount, source_id, destination_id, posted, description, payee, transacted_at):\n self.id = id\n self.amount = amount\n self.source_id = source_id\n self.destination_id = destination_id\n self.posted = posted\n self.description = description\n self.payee = payee\n self.transacted_at = transacted_at\n\n def to_dict(self):\n return {\n 'id': self.id,\n 'type': 'transfer',\n 'amount': self.amount,\n 'source_id': self.source_id,\n 'destination_id': self.destination_id,\n 'posted': self.posted.timestamp(),\n 'description': self.description,\n 'payee': self.payee,\n 'transacted_at': self.transacted_at.timestamp(),\n }\n\n \n\ntransactions=[]\nfor item in _input.all():\n t=item.json\n src_id = t['source_id'] if 'source_id' in t else \"\"\n dest_id = t['destination_id'] if 'destination_id' in t else \"\"\n description = \" \".join(t['description'].strip().split())\n payee = \" \".join(t['payee'].strip().split())\n transaction = Transaction(t['id'], t['type'], t['amount'], t['bank'], src_id, dest_id, t['posted'], description, payee, t['transacted_at'])\n transactions.append(transaction)\n\n\n# Group transactions by type and amount\ngrouped_transactions = defaultdict(lambda: defaultdict(list))\nfor t in transactions:\n grouped_transactions[t.type][t.amount].append(t)\n\n# Find transfers\nfor amount, deposit_transactions in grouped_transactions['deposit'].items():\n withdrawal_transactions = grouped_transactions['withdrawal'].get(amount, [])\n\n for deposit in deposit_transactions:\n for withdrawal in withdrawal_transactions:\n # If banks are different, posted times are within 1 day and descriptions contain required keywords\n if deposit.bank != withdrawal.bank and abs(deposit.posted - withdrawal.posted) <= timedelta(days=timeDeltaDays) and (\n any(\n keyword.lower() in deposit.description.lower() for keyword in keywords) or (any(\n keyword.lower() in withdrawal.description.lower() for keyword in keywords))):\n transfer = Transfer(withdrawal.id + deposit.id, deposit.amount, withdrawal.bank, deposit.bank,\n max(deposit.posted, withdrawal.posted),\n withdrawal.description + \" - \" + deposit.description, deposit.payee,\n deposit.transacted_at)\n transactions.remove(deposit)\n transactions.remove(withdrawal)\n withdrawal_transactions.remove(withdrawal)\n transactions.append(transfer)\n break\n\noutput=[]\nfor i in transactions:\n output.append(i.to_dict())\n\nreturn output" | |
}, | |
"id": "1a5421c8-ce3a-48f2-8baf-3b6a1e5637c6", | |
"name": "Transfer Transactions", | |
"type": "n8n-nodes-base.code", | |
"typeVersion": 2, | |
"position": [ | |
980, | |
340 | |
] | |
}, | |
{ | |
"parameters": { | |
"url": "=https://beta-bridge.simplefin.org/simplefin/accounts", | |
"authentication": "genericCredentialType", | |
"genericAuthType": "httpBasicAuth", | |
"sendQuery": true, | |
"queryParameters": { | |
"parameters": [ | |
{ | |
"name": "start-date", | |
"value": "={{ Math.floor(new Date().setDate(new Date().getDate() -5 )/1000);}}" | |
} | |
] | |
}, | |
"options": {} | |
}, | |
"id": "9f1759e6-68fe-45ee-9959-5e9b084ac2a7", | |
"name": "SimpleFin", | |
"type": "n8n-nodes-base.httpRequest", | |
"typeVersion": 4.2, | |
"position": [ | |
740, | |
260 | |
], | |
"credentials": { | |
"httpBasicAuth": { | |
"id": "8HQHP0oGrWIVg7HM", | |
"name": "SimpleFIN Basic Auth" | |
} | |
} | |
}, | |
{ | |
"parameters": { | |
"method": "POST", | |
"url": "https://firefly.paid.lol/api/v1/transactions", | |
"authentication": "genericCredentialType", | |
"genericAuthType": "httpHeaderAuth", | |
"sendBody": true, | |
"specifyBody": "json", | |
"jsonBody": "={\n \"error_if_duplicate_hash\": true,\n \"apply_rules\": true,\n \"fire_webhooks\": true,\n \"group_title\": \"\",\n \"transactions\": [\n {\n \"type\": \"{{ $json[\"type\"] }}\",\n \"date\": \"{{ $json[\"transacted_at\"].toDateTime(\"s\").toISO() }}\",\n \"amount\": \"{{ $json[\"amount\"] }}\",\n \"description\": \"{{ $json[\"description\"] }}\",\n \"source_id\": \"{{ $json[\"source_id\"] }}\",\n \"destination_id\": \"{{ $json[\"destination_id\"] }}\",\n \"process_date\": \"{{ $json[\"posted\"].toDateTime(\"s\").toISO() }}\",\n \"destination_name\": \"{{ $json[\"payee\"] }}\",\n \"external_id\":\"{{ $json[\"id\"] }}\"\n } \n ]\n}", | |
"options": {} | |
}, | |
"id": "981cdc9e-e324-4fdf-b664-912079926c79", | |
"name": "Firefly Request", | |
"type": "n8n-nodes-base.httpRequest", | |
"typeVersion": 4.2, | |
"position": [ | |
1180, | |
340 | |
], | |
"credentials": { | |
"httpHeaderAuth": { | |
"id": "OixlU0thMigFLiC3", | |
"name": "Firefly Header Auth" | |
} | |
}, | |
"onError": "continueErrorOutput" | |
}, | |
{ | |
"parameters": { | |
"rule": { | |
"interval": [ | |
{ | |
"triggerAtHour": 14 | |
} | |
] | |
} | |
}, | |
"id": "010b5ff8-bcf7-4133-8f36-06a373e2ec6c", | |
"name": "Daily at 2pm", | |
"type": "n8n-nodes-base.scheduleTrigger", | |
"typeVersion": 1.2, | |
"position": [ | |
540, | |
260 | |
] | |
}, | |
{ | |
"parameters": { | |
"rules": { | |
"values": [ | |
{ | |
"conditions": { | |
"options": { | |
"caseSensitive": true, | |
"leftValue": "", | |
"typeValidation": "strict" | |
}, | |
"conditions": [ | |
{ | |
"leftValue": "={{ $json.errors }}", | |
"rightValue": "", | |
"operator": { | |
"type": "array", | |
"operation": "notEmpty", | |
"singleValue": true | |
} | |
} | |
], | |
"combinator": "and" | |
} | |
} | |
] | |
}, | |
"options": {} | |
}, | |
"id": "f1d64143-35f5-4fe7-96c5-9c4d534eeb27", | |
"name": "Switch", | |
"type": "n8n-nodes-base.switch", | |
"typeVersion": 3, | |
"position": [ | |
780, | |
100 | |
] | |
} | |
], | |
"pinData": {}, | |
"connections": { | |
"Transactions": { | |
"main": [ | |
[ | |
{ | |
"node": "Transfer Transactions", | |
"type": "main", | |
"index": 0 | |
} | |
] | |
] | |
}, | |
"SimpleFin": { | |
"main": [ | |
[ | |
{ | |
"node": "Transactions", | |
"type": "main", | |
"index": 0 | |
}, | |
{ | |
"node": "Switch", | |
"type": "main", | |
"index": 0 | |
} | |
] | |
] | |
}, | |
"Transfer Transactions": { | |
"main": [ | |
[ | |
{ | |
"node": "Firefly Request", | |
"type": "main", | |
"index": 0 | |
} | |
] | |
] | |
}, | |
"Firefly Request": { | |
"main": [ | |
[], | |
[ | |
{ | |
"node": "Code", | |
"type": "main", | |
"index": 0 | |
} | |
] | |
] | |
}, | |
"Daily at 2pm": { | |
"main": [ | |
[ | |
{ | |
"node": "SimpleFin", | |
"type": "main", | |
"index": 0 | |
} | |
] | |
] | |
}, | |
"Switch": { | |
"main": [ | |
[ | |
{ | |
"node": "Gotify", | |
"type": "main", | |
"index": 0 | |
} | |
] | |
] | |
} | |
}, | |
"active": false, | |
"settings": { | |
"executionOrder": "v1", | |
"saveManualExecutions": true, | |
"callerPolicy": "workflowsFromSameOwner" | |
}, | |
"versionId": "063bb1d8-6cc0-473c-a2f7-c251ca95f68b", | |
"meta": { | |
"templateCredsSetupCompleted": true, | |
"instanceId": "5e1619f539bf4fca477bbc4fab24e9f25d671f9e9b0589672c29f0bdbe199d84" | |
}, | |
"id": "jO9rvClvsHA3ZFia", | |
"tags": [] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment