Implement viewing bank balances
This commit is contained in:
parent
1ee3d04ea6
commit
35ffa036ae
|
@ -105,4 +105,10 @@ urlpatterns = [
|
|||
banks.BanksCurrencies.as_view(),
|
||||
name="currencies",
|
||||
),
|
||||
# Bank balances
|
||||
path(
|
||||
"banks/<str:type>/balances/",
|
||||
banks.BanksBalances.as_view(),
|
||||
name="balances",
|
||||
),
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
|
|
@ -3,7 +3,6 @@ from abc import ABC
|
|||
|
||||
class AggregatorClient(ABC):
|
||||
def store_account_info(self, account_infos):
|
||||
print("STORE ACCOUNT INFO CALLED")
|
||||
# account_infos = {
|
||||
# bank: accounts
|
||||
# for bank, accounts in account_info.items()
|
||||
|
@ -41,6 +40,3 @@ class AggregatorClient(ABC):
|
|||
|
||||
self.instance.currencies = currencies
|
||||
self.instance.save()
|
||||
|
||||
print("CURRENCIES", self.instance.currencies)
|
||||
print("ACCOUNT INFO", self.instance.account_info)
|
||||
|
|
|
@ -61,7 +61,7 @@ class NordigenClient(BaseClient, AggregatorClient):
|
|||
"""
|
||||
# This function is a stub.
|
||||
|
||||
return ["GB", "SE", "BG", "UA"]
|
||||
return ["GB", "SE", "BG"]
|
||||
|
||||
async def get_banks(self, country):
|
||||
"""
|
||||
|
@ -129,7 +129,6 @@ class NordigenClient(BaseClient, AggregatorClient):
|
|||
|
||||
path = f"accounts/{account_id}/details"
|
||||
response = await self.call(path, schema="AccountDetails")
|
||||
print("RESPONSE", response)
|
||||
if "account" not in response:
|
||||
return False
|
||||
parsed = response["account"]
|
||||
|
@ -151,9 +150,7 @@ class NordigenClient(BaseClient, AggregatorClient):
|
|||
async def get_all_account_info(self, requisition=None, store=False):
|
||||
to_return = {}
|
||||
if not requisition:
|
||||
print("NOT REQUISITION")
|
||||
requisitions = await self.get_requisitions()
|
||||
print("GOT REQS", requisitions)
|
||||
else:
|
||||
requisitions = [await self.get_requisition(requisition)]
|
||||
|
||||
|
@ -169,6 +166,78 @@ class NordigenClient(BaseClient, AggregatorClient):
|
|||
to_return[req["institution_id"]] = [account_info]
|
||||
|
||||
if store:
|
||||
print("TO STORE IS TRUE")
|
||||
if requisition is not None:
|
||||
raise Exception("Cannot store partial data")
|
||||
self.store_account_info(to_return)
|
||||
return to_return
|
||||
|
||||
async def get_balance(self, account_id):
|
||||
"""
|
||||
Get the balance and currency of an account.
|
||||
:param account_id: the account ID
|
||||
:return: tuple of (currency, amount)
|
||||
:rtype: tuple
|
||||
"""
|
||||
|
||||
path = f"accounts/{account_id}/balances"
|
||||
response = await self.call(path, schema="AccountBalance")
|
||||
|
||||
total = 0
|
||||
currency = None
|
||||
if "balances" not in response:
|
||||
return (False, False)
|
||||
for entry in response["balances"]:
|
||||
if currency:
|
||||
if not currency == entry["balanceAmount"]["currency"]:
|
||||
return (False, False)
|
||||
total += float(entry["balanceAmount"]["amount"])
|
||||
currency = entry["balanceAmount"]["currency"]
|
||||
return (currency, total)
|
||||
|
||||
async def get_all_balances(self):
|
||||
"""
|
||||
Get all balances.
|
||||
Keyed by bank.
|
||||
"""
|
||||
if self.instance.account_info is None:
|
||||
await self.get_all_account_info(store=True)
|
||||
|
||||
account_balances = {}
|
||||
for bank, accounts in self.instance.account_info.items():
|
||||
if bank not in account_balances:
|
||||
account_balances[bank] = []
|
||||
for account in accounts:
|
||||
account_id = account["account_id"]
|
||||
currency, amount = await self.get_balance(account_id)
|
||||
account_balances[bank].append(
|
||||
{
|
||||
"currency": currency,
|
||||
"balance": amount,
|
||||
"account_id": account_id,
|
||||
}
|
||||
)
|
||||
return account_balances
|
||||
|
||||
async def get_total_map(self):
|
||||
"""
|
||||
Return a dictionary keyed by currencies with the amounts as values.
|
||||
:return: dict keyed by currency, values are amounts
|
||||
:rtype: dict
|
||||
"""
|
||||
if self.instance.account_info is None:
|
||||
await self.get_all_account_info(store=True)
|
||||
|
||||
totals = {}
|
||||
for bank, accounts in self.instance.account_info.items():
|
||||
for account in accounts:
|
||||
account_id = account["account_id"]
|
||||
currency, amount = await self.get_balance(account_id)
|
||||
if not amount:
|
||||
continue
|
||||
if not currency:
|
||||
continue
|
||||
if currency in totals:
|
||||
totals[currency] += amount
|
||||
else:
|
||||
totals[currency] = amount
|
||||
return totals
|
||||
|
|
|
@ -18,9 +18,7 @@ async def job():
|
|||
for aggregator in aggregators:
|
||||
if aggregator.service == "nordigen":
|
||||
instance = await NordigenClient(aggregator)
|
||||
print("RUNNING GET ALL ACCOUNT INFO")
|
||||
await instance.get_all_account_info(store=True)
|
||||
print("FINISHED RUNNING")
|
||||
else:
|
||||
raise NotImplementedError(f"No such client library: {aggregator.service}")
|
||||
aggregator.fetch_accounts = False
|
||||
|
|
|
@ -231,7 +231,7 @@
|
|||
<a class="navbar-item" href="{% url 'currencies' type='page' %}">
|
||||
Currencies
|
||||
</a>
|
||||
<a class="navbar-item" href="#">
|
||||
<a class="navbar-item" href="{% url 'balances' type='page' %}">
|
||||
Balances
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
{% load cache %}
|
||||
{% load cachalot cache %}
|
||||
{% load nsep %}
|
||||
|
||||
{% get_last_invalidation 'core.Aggregator' as last %}
|
||||
{% include 'mixins/partials/notify.html' %}
|
||||
{# cache 600 objects_banks_balances request.user.id object_list type last #}
|
||||
|
||||
{% for bank, accounts in object_list.items %}
|
||||
<h1 class="title is-4">{{ bank }}</h1>
|
||||
<table
|
||||
class="table is-fullwidth is-hoverable"
|
||||
hx-target="#{{ bank }}-table"
|
||||
id="{{ bank }}-table"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="{{ context_object_name_singular }}Event from:body"
|
||||
hx-get="{{ list_url }}">
|
||||
<thead>
|
||||
<th>currency</th>
|
||||
<th>balance</th>
|
||||
<th>id</th>
|
||||
|
||||
</thead>
|
||||
{% for account in accounts %}
|
||||
<tr>
|
||||
<td>{{ account.currency }}</td>
|
||||
<td>{{ account.balance }}</td>
|
||||
<td>
|
||||
<a
|
||||
class="has-text-grey"
|
||||
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{ account.account_id }}');">
|
||||
<span class="icon" data-tooltip="Copy to clipboard">
|
||||
<i class="fa-solid fa-copy" aria-hidden="true"></i>
|
||||
</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
{% endfor %}
|
||||
{# endcache #}
|
|
@ -1,16 +1,18 @@
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from mixins.views import ObjectList, ObjectRead
|
||||
from mixins.views import ObjectList
|
||||
from two_factor.views.mixins import OTPRequiredMixin
|
||||
|
||||
from core.clients.aggregators.nordigen import NordigenClient
|
||||
from core.models import Aggregator
|
||||
from core.util import logs
|
||||
from core.views.helpers import synchronize_async_helper
|
||||
|
||||
log = logs.get_logger(__name__)
|
||||
|
||||
|
||||
class BanksCurrencies(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
||||
"""
|
||||
Get a list of configured currencies from the banks we use.
|
||||
Get a list of bank accounts with their details.
|
||||
"""
|
||||
|
||||
list_template = "partials/banks-currencies-list.html"
|
||||
|
@ -36,7 +38,30 @@ class BanksCurrencies(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
|||
return account_info
|
||||
|
||||
|
||||
class BankCurrencyDetails(LoginRequiredMixin, OTPRequiredMixin, ObjectRead):
|
||||
class BanksBalances(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
||||
"""
|
||||
Get the bank details for the selected currency.
|
||||
Get the bank balances.
|
||||
"""
|
||||
|
||||
list_template = "partials/banks-balances-list.html"
|
||||
page_title = "Bank Balances"
|
||||
|
||||
context_object_name_singular = "balance"
|
||||
context_object_name = "balances"
|
||||
|
||||
list_url_name = "balances"
|
||||
list_url_args = ["type"]
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
aggregators = Aggregator.objects.filter(user=self.request.user, enabled=True)
|
||||
account_balances = {}
|
||||
for aggregator in aggregators:
|
||||
run = synchronize_async_helper(NordigenClient(aggregator))
|
||||
balance_map = synchronize_async_helper(run.get_all_balances())
|
||||
for k, v in balance_map.items():
|
||||
if k not in account_balances:
|
||||
account_balances[k] = []
|
||||
for item in v:
|
||||
account_balances[k].append(item)
|
||||
|
||||
return account_balances
|
||||
|
|
Loading…
Reference in New Issue