diff --git a/app/apps/net_worth/tests.py b/app/apps/net_worth/tests.py index 7ce503c..36c96d4 100644 --- a/app/apps/net_worth/tests.py +++ b/app/apps/net_worth/tests.py @@ -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, + 'Consolidated', + html=False, + ) diff --git a/app/apps/net_worth/views.py b/app/apps/net_worth/views.py index 57687d7..f8edacc 100644 --- a/app/apps/net_worth/views.py +++ b/app/apps/net_worth/views.py @@ -3,8 +3,11 @@ import json from django.contrib.auth.decorators import login_required from django.core.serializers.json import DjangoJSONEncoder from django.shortcuts import render, redirect +from django.utils.translation import gettext 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 ( calculate_historical_currency_net_worth, calculate_historical_account_balance, @@ -78,6 +81,17 @@ def net_worth(request): ) 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): data = [ 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_json = json.dumps(chart_data_currency, cls=DjangoJSONEncoder) diff --git a/app/templates/net_worth/net_worth.html b/app/templates/net_worth/net_worth.html index 438e1f3..f882a95 100644 --- a/app/templates/net_worth/net_worth.html +++ b/app/templates/net_worth/net_worth.html @@ -56,7 +56,7 @@