mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-08-30 07:07:23 +02:00
feat(net-worth): add consolidated chart lines
This commit is contained in:
@@ -1,3 +1,82 @@
|
|||||||
from django.test import TestCase
|
import json
|
||||||
|
import tempfile
|
||||||
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
# Create your tests here.
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.test import TestCase, override_settings
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from apps.accounts.models import Account
|
||||||
|
from apps.currencies.models import Currency, ExchangeRate
|
||||||
|
from apps.transactions.models import Transaction
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
STATIC_ROOT=tempfile.gettempdir(),
|
||||||
|
STORAGES={
|
||||||
|
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
|
||||||
|
"staticfiles": {
|
||||||
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
class NetWorthCurrencyChartTests(TestCase):
|
||||||
|
def test_consolidated_currency_is_a_selectable_dashed_matching_color_line(self):
|
||||||
|
user = get_user_model().objects.create_user(
|
||||||
|
email="chart@example.com", password="password"
|
||||||
|
)
|
||||||
|
usd = Currency.objects.create(code="USD", name="US Dollar", prefix="$ ")
|
||||||
|
eur = Currency.objects.create(
|
||||||
|
code="EUR", name="Euro", prefix="€ ", exchange_currency=usd
|
||||||
|
)
|
||||||
|
usd_account = Account.all_objects.create(
|
||||||
|
name="USD account", currency=usd, owner=user
|
||||||
|
)
|
||||||
|
eur_account = Account.all_objects.create(
|
||||||
|
name="EUR account", currency=eur, owner=user
|
||||||
|
)
|
||||||
|
ExchangeRate.objects.create(
|
||||||
|
from_currency=eur,
|
||||||
|
to_currency=usd,
|
||||||
|
rate=Decimal("1.234567"),
|
||||||
|
date=timezone.now(),
|
||||||
|
)
|
||||||
|
for account, amount in ((usd_account, "100"), (eur_account, "50")):
|
||||||
|
Transaction.userless_all_objects.create(
|
||||||
|
account=account,
|
||||||
|
owner=user,
|
||||||
|
type=Transaction.Type.INCOME,
|
||||||
|
amount=Decimal(amount),
|
||||||
|
date=date(2026, 1, 15),
|
||||||
|
reference_date=date(2026, 1, 1),
|
||||||
|
is_paid=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.force_login(user)
|
||||||
|
response = self.client.get(reverse("net_worth"))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
chart_data = json.loads(response.context["chart_data_currency_json"])
|
||||||
|
datasets = {dataset["label"]: dataset for dataset in chart_data["datasets"]}
|
||||||
|
self.assertIn("US Dollar Consolidated", datasets)
|
||||||
|
regular = datasets["US Dollar"]
|
||||||
|
consolidated = datasets["US Dollar Consolidated"]
|
||||||
|
self.assertEqual(consolidated["data"], [161.73])
|
||||||
|
self.assertNotIn("borderColor", regular)
|
||||||
|
self.assertNotIn("borderColor", consolidated)
|
||||||
|
self.assertEqual(consolidated["colorSource"], "US Dollar")
|
||||||
|
self.assertEqual(consolidated["borderDash"], [12, 6])
|
||||||
|
self.assertEqual(consolidated["pointRadius"], 0)
|
||||||
|
self.assertEqual(consolidated["pointHitRadius"], 8)
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
"showOnlyCurrencyDataset('US Dollar Consolidated', 'US Dollar')",
|
||||||
|
html=False,
|
||||||
|
)
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
'<span class="text-start shrink">Consolidated</span>',
|
||||||
|
html=False,
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ import json
|
|||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
|
from django.utils.translation import gettext
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
|
|
||||||
|
from apps.currencies.models import Currency
|
||||||
|
from apps.currencies.utils.convert import convert
|
||||||
from apps.net_worth.utils.calculate_net_worth import (
|
from apps.net_worth.utils.calculate_net_worth import (
|
||||||
calculate_historical_currency_net_worth,
|
calculate_historical_currency_net_worth,
|
||||||
calculate_historical_account_balance,
|
calculate_historical_account_balance,
|
||||||
@@ -78,6 +81,17 @@ def net_worth(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
datasets = []
|
datasets = []
|
||||||
|
currency_models = {
|
||||||
|
currency.name: currency
|
||||||
|
for currency in Currency.objects.filter(name__in=currencies).select_related(
|
||||||
|
"exchange_currency"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
consolidated_currencies = {
|
||||||
|
data["currency"]["name"]
|
||||||
|
for data in currency_net_worth.values()
|
||||||
|
if data["consolidated"]["total_final"] != data["total_final"]
|
||||||
|
}
|
||||||
for i, currency in enumerate(currencies):
|
for i, currency in enumerate(currencies):
|
||||||
data = [
|
data = [
|
||||||
float(month_data[currency])
|
float(month_data[currency])
|
||||||
@@ -93,6 +107,45 @@ def net_worth(request):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if currency in consolidated_currencies:
|
||||||
|
target = currency_models[currency]
|
||||||
|
sources = [
|
||||||
|
source
|
||||||
|
for source in currency_models.values()
|
||||||
|
if source.exchange_currency_id == target.id
|
||||||
|
]
|
||||||
|
rates = {}
|
||||||
|
for source in sources:
|
||||||
|
converted, _, _, _ = convert(1, source, target)
|
||||||
|
if converted is not None:
|
||||||
|
rates[source.name] = converted
|
||||||
|
|
||||||
|
consolidated_data = [
|
||||||
|
float(
|
||||||
|
round(
|
||||||
|
month_data[currency]
|
||||||
|
+ sum(
|
||||||
|
month_data[source] * rate for source, rate in rates.items()
|
||||||
|
),
|
||||||
|
target.decimal_places,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for month_data in historical_currency_net_worth.values()
|
||||||
|
]
|
||||||
|
datasets.append(
|
||||||
|
{
|
||||||
|
"label": f"{currency} {gettext('Consolidated')}",
|
||||||
|
"data": consolidated_data,
|
||||||
|
"yAxisID": f"y{i}",
|
||||||
|
"fill": False,
|
||||||
|
"tension": 0.1,
|
||||||
|
"colorSource": currency,
|
||||||
|
"borderDash": [12, 6],
|
||||||
|
"pointRadius": 0,
|
||||||
|
"pointHitRadius": 8,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
chart_data_currency = {"labels": labels, "datasets": datasets}
|
chart_data_currency = {"labels": labels, "datasets": datasets}
|
||||||
|
|
||||||
chart_data_currency_json = json.dumps(chart_data_currency, cls=DjangoJSONEncoder)
|
chart_data_currency_json = json.dumps(chart_data_currency, cls=DjangoJSONEncoder)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<li>
|
<li>
|
||||||
{% if currency.consolidated and currency.consolidated.total_final != currency.total_final %}
|
{% if currency.consolidated and currency.consolidated.total_final != currency.total_final %}
|
||||||
<a class="cursor-pointer select-auto flex justify-between items-center w-full"
|
<a class="cursor-pointer select-auto flex justify-between items-center w-full"
|
||||||
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')">
|
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}', '{{ currency.currency.name }}')">
|
||||||
<span
|
<span
|
||||||
class="currency-name text-start font-mono shrink text-ellipsis">{{ currency.currency.name }}</span>
|
class="currency-name text-start font-mono shrink text-ellipsis">{{ currency.currency.name }}</span>
|
||||||
<span class="text-end shrink-0">
|
<span class="text-end shrink-0">
|
||||||
@@ -80,7 +80,8 @@
|
|||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a class="text-base-content/60 select-auto flex justify-between items-center w-full">
|
<a class="cursor-pointer text-base-content/60 select-auto flex justify-between items-center w-full"
|
||||||
|
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }} {% trans "Consolidated" %}', '{{ currency.currency.name }}')">
|
||||||
<span class="text-start shrink">{% trans "Consolidated" %}</span>
|
<span class="text-start shrink">{% trans "Consolidated" %}</span>
|
||||||
<span class="text-end shrink-0">
|
<span class="text-end shrink-0">
|
||||||
<c-amount.display :amount="currency.consolidated.total_final"
|
<c-amount.display :amount="currency.consolidated.total_final"
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="cursor-pointer select-auto flex justify-between items-center w-full"
|
<a class="cursor-pointer select-auto flex justify-between items-center w-full"
|
||||||
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')">
|
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}', '{{ currency.currency.name }}')">
|
||||||
<span class="currency-name text-start font-mono shrink">{{ currency.currency.name }}</span>
|
<span class="currency-name text-start font-mono shrink">{{ currency.currency.name }}</span>
|
||||||
<span class="text-end shrink-0">
|
<span class="text-end shrink-0">
|
||||||
<div>
|
<div>
|
||||||
@@ -302,6 +303,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const dataset of currencyChart.data.datasets) {
|
||||||
|
if (!dataset.colorSource) continue;
|
||||||
|
const source = currencyChart.data.datasets.find(
|
||||||
|
candidate => candidate.label === dataset.colorSource
|
||||||
|
);
|
||||||
|
dataset.borderColor = source.borderColor;
|
||||||
|
dataset.backgroundColor = source.backgroundColor;
|
||||||
|
}
|
||||||
|
currencyChart.update('none');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script id="accountBalanceChartScript">
|
<script id="accountBalanceChartScript">
|
||||||
@@ -448,7 +459,7 @@
|
|||||||
call accountChart.update()
|
call accountChart.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
def showOnlyCurrencyDataset(datasetName)
|
def showOnlyCurrencyDataset(datasetName, differenceDatasetName)
|
||||||
for dataset in currencyChart.data.datasets
|
for dataset in currencyChart.data.datasets
|
||||||
set isMatch to dataset.label is datasetName
|
set isMatch to dataset.label is datasetName
|
||||||
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), isMatch)
|
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), isMatch)
|
||||||
@@ -456,7 +467,7 @@
|
|||||||
call currencyChart.update()
|
call currencyChart.update()
|
||||||
|
|
||||||
for dataset in monthlyDifferenceChart.data.datasets
|
for dataset in monthlyDifferenceChart.data.datasets
|
||||||
set isMatch to dataset.label is datasetName
|
set isMatch to dataset.label is differenceDatasetName
|
||||||
call monthlyDifferenceChart.setDatasetVisibility(monthlyDifferenceChart.data.datasets.indexOf(dataset), isMatch)
|
call monthlyDifferenceChart.setDatasetVisibility(monthlyDifferenceChart.data.datasets.indexOf(dataset), isMatch)
|
||||||
end
|
end
|
||||||
call monthlyDifferenceChart.update()
|
call monthlyDifferenceChart.update()
|
||||||
|
|||||||
Reference in New Issue
Block a user