diff --git a/app/apps/net_worth/utils/calculate_net_worth.py b/app/apps/net_worth/utils/calculate_net_worth.py index 54c75c7..c63ce50 100644 --- a/app/apps/net_worth/utils/calculate_net_worth.py +++ b/app/apps/net_worth/utils/calculate_net_worth.py @@ -108,22 +108,39 @@ def calculate_currency_net_worth(): .values("balance") ) - # Main query to fetch all currency data + # Fetch all currencies with their balances in a single query currencies_data = Currency.objects.annotate( balance=Coalesce(Subquery(balance_subquery), Decimal("0")) - ) + ).select_related( + "exchange_currency" + ) # Optimize by pre-fetching exchange_currency net_worth = {} - for item in currencies_data: - currency_name = item.name - net_worth[currency_name] = { - "amount": net_worth.get(currency_name, {}).get("amount", Decimal("0")) - + item.balance, - "code": item.code, - "name": currency_name, - "prefix": item.prefix, - "suffix": item.suffix, - "decimal_places": item.decimal_places, + for currency in currencies_data: + # Skip conversion if no exchange currency is set + exchanged_value = None + if currency.exchange_currency: + exchanged_amount, ex_prefix, ex_suffix, ex_decimal_places = convert( + amount=currency.balance, + from_currency=currency, + to_currency=currency.exchange_currency, + ) + exchanged_value = { + "amount": exchanged_amount, + "name": currency.exchange_currency.name, + "prefix": ex_prefix, + "suffix": ex_suffix, + "decimal_places": ex_decimal_places, + } + + net_worth[currency.name] = { + "amount": currency.balance, + "code": currency.code, + "name": currency.name, + "prefix": currency.prefix, + "suffix": currency.suffix, + "decimal_places": currency.decimal_places, + "exchanged": exchanged_value, } return net_worth diff --git a/app/apps/yearly_overview/views.py b/app/apps/yearly_overview/views.py index e007c67..7bb8782 100644 --- a/app/apps/yearly_overview/views.py +++ b/app/apps/yearly_overview/views.py @@ -73,14 +73,13 @@ def yearly_overview_by_currency(request, year: int): if currency: filter_params["account__currency_id"] = int(currency) - transactions = Transaction.objects.filter(**filter_params).exclude( - Q(category__mute=True) & ~Q(category=None) + transactions = ( + Transaction.objects.filter(**filter_params) + .exclude(Q(category__mute=True) & ~Q(category=None)) + .select_related("account__currency") # Optimize by pre-fetching currency data ) - if month: - date_trunc = TruncMonth("reference_date") - else: - date_trunc = TruncYear("reference_date") + date_trunc = TruncMonth("reference_date") if month else TruncYear("reference_date") monthly_data = ( transactions.annotate(month=date_trunc) @@ -90,6 +89,8 @@ def yearly_overview_by_currency(request, year: int): "account__currency__prefix", "account__currency__suffix", "account__currency__decimal_places", + "account__currency_id", + "account__currency__exchange_currency_id", ) .annotate( income_paid=Coalesce( @@ -103,7 +104,6 @@ def yearly_overview_by_currency(request, year: int): ) ), Value(Decimal("0")), - output_field=DecimalField(), ), expense_paid=Coalesce( Sum( @@ -118,7 +118,6 @@ def yearly_overview_by_currency(request, year: int): ) ), Value(Decimal("0")), - output_field=DecimalField(), ), income_unpaid=Coalesce( Sum( @@ -133,7 +132,6 @@ def yearly_overview_by_currency(request, year: int): ) ), Value(Decimal("0")), - output_field=DecimalField(), ), expense_unpaid=Coalesce( Sum( @@ -148,7 +146,6 @@ def yearly_overview_by_currency(request, year: int): ) ), Value(Decimal("0")), - output_field=DecimalField(), ), ) .annotate( @@ -162,7 +159,12 @@ def yearly_overview_by_currency(request, year: int): .order_by("month", "account__currency__code") ) - # Create a dictionary to store the final result + # Fetch all currencies and their exchange currencies in a single query + currencies = { + currency.id: currency + for currency in Currency.objects.select_related("exchange_currency").all() + } + result = { "income_paid": [], "expense_paid": [], @@ -173,32 +175,53 @@ def yearly_overview_by_currency(request, year: int): "balance_total": [], } - # Fill in the data for entry in monthly_data: + if all(entry[field] == 0 for field in result.keys()): + continue # Skip entries where all values are 0 + currency_code = entry["account__currency__code"] prefix = entry["account__currency__prefix"] suffix = entry["account__currency__suffix"] decimal_places = entry["account__currency__decimal_places"] - for field in [ - "income_paid", - "expense_paid", - "income_unpaid", - "expense_unpaid", - "balance_unpaid", - "balance_paid", - "balance_total", - ]: - if entry[field] != 0: - result[field].append( - { - "code": currency_code, - "prefix": prefix, - "suffix": suffix, - "decimal_places": decimal_places, - "amount": entry[field], - } + # Get the currency objects for conversion + from_currency = currencies.get(entry["account__currency_id"]) + to_currency = ( + None + if not from_currency + else currencies.get(from_currency.exchange_currency_id) + ) + + for field in result.keys(): + amount = entry[field] + if amount == 0: + continue + + item = { + "code": currency_code, + "prefix": prefix, + "suffix": suffix, + "decimal_places": decimal_places, + "amount": amount, + "exchanged": None, + } + + # Add exchange calculation if possible + if from_currency and to_currency: + exchanged_amount, ex_prefix, ex_suffix, ex_decimal_places = convert( + amount=amount, + from_currency=from_currency, + to_currency=to_currency, ) + item["exchanged"] = { + "amount": exchanged_amount, + "code": to_currency.code, + "prefix": ex_prefix, + "suffix": ex_suffix, + "decimal_places": ex_decimal_places, + } + + result[field].append(item) return render( request, diff --git a/app/templates/cotton/amount/display.html b/app/templates/cotton/amount/display.html index bb8a720..c8905aa 100644 --- a/app/templates/cotton/amount/display.html +++ b/app/templates/cotton/amount/display.html @@ -1,7 +1,7 @@ {% load currency_display %}